mirror of
https://github.com/CannedNet/CannedNet.git
synced 2026-10-25 23:18:54 -07:00
a few changes
- fixes nullrefs on notifications
This commit is contained in:
@@ -50,3 +50,4 @@ public class NotificationsHub : Hub
|
||||
return Context.GetHttpContext()?.RequestServices.GetService(typeof(T)) as T;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"CDN": "https://cdn.lapis.codes",
|
||||
"Commerce": "https://commerce.lapis.codes",
|
||||
"Matchmaking": "https://match.lapis.codes",
|
||||
"Storage": "https://storage.lapis.codes",
|
||||
"Storage": "https://cdn.lapis.codes",
|
||||
"Chat": "https://chatrr.lapis.codes",
|
||||
"Leaderboard": "https://leaderboard.lapis.codes",
|
||||
"Accounts": "https://accounts.lapis.codes",
|
||||
|
||||
@@ -21,6 +21,8 @@ public class APIController
|
||||
var storefrontService = app.Services.GetRequiredService<StorefrontFillService>();
|
||||
var notificationService = app.Services.GetRequiredService<NotificationService>();
|
||||
|
||||
MapImageEndpoints(app);
|
||||
|
||||
app.MapGet("/api/config/v1/amplitude", () => Results.Ok(new
|
||||
{
|
||||
AmplitudeKey = "a",
|
||||
@@ -1371,11 +1373,11 @@ public class APIController
|
||||
var receivedGift = new ReceivedGift
|
||||
{
|
||||
ReceiverAccountId = receiverAccountId,
|
||||
FromPlayerId = senderPlayerId,
|
||||
FromPlayerId = 1,
|
||||
Message = buyRequest.Gift?.Message ?? "A gift for you <3",
|
||||
ConsumableItemDesc = giftDrop.ConsumableItemDesc ?? string.Empty,
|
||||
AvatarItemDesc = giftDrop.AvatarItemDesc ?? string.Empty,
|
||||
FriendlyName = giftDrop.FriendlyName ?? string.Empty,
|
||||
// FriendlyName = giftDrop.FriendlyName ?? string.Empty,
|
||||
AvatarItemType = giftDrop.AvatarItemType,
|
||||
EquipmentPrefabName = giftDrop.EquipmentPrefabName ?? string.Empty,
|
||||
EquipmentModificationGuid = giftDrop.EquipmentModificationGuid ?? string.Empty,
|
||||
@@ -1442,10 +1444,10 @@ public class APIController
|
||||
toAccountId: id,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
{ "ItemId", buyRequest.PurchasableItemId },
|
||||
{ "Price", price.Price },
|
||||
{ "CurrencyType", buyRequest.CurrencyType },
|
||||
{ "NewBalance", tokenBalance.Balance }
|
||||
{ "BalanceAddType", 1400 },
|
||||
{ "Delta", -price.Price },
|
||||
{ "Balance", tokenBalance.Balance },
|
||||
{ "CurrencyType", buyRequest.CurrencyType }
|
||||
}
|
||||
);
|
||||
await notificationService.SendNotificationToPlayer(id, purchaseNotification);
|
||||
@@ -1570,7 +1572,7 @@ public class APIController
|
||||
var receivedGift = new ReceivedGift
|
||||
{
|
||||
ReceiverAccountId = id,
|
||||
FromPlayerId = null,
|
||||
FromPlayerId = 1,
|
||||
Message = message,
|
||||
ConsumableItemDesc = consumableItemDesc,
|
||||
AvatarItemDesc = avatarItemDesc,
|
||||
@@ -1597,7 +1599,7 @@ public class APIController
|
||||
var giftData = new
|
||||
{
|
||||
Id = receivedGift.Id,
|
||||
FromPlayerId = (object?)null,
|
||||
FromPlayerId = 1,
|
||||
ConsumableItemDesc = consumableItemDesc,
|
||||
AvatarItemDesc = avatarItemDesc,
|
||||
FriendlyName = friendlyName,
|
||||
@@ -1746,39 +1748,7 @@ public class APIController
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(receivedGift.ConsumableItemDesc))
|
||||
{
|
||||
var consumableNotification = notificationService.CreateNotification(
|
||||
PushNotificationId.ConsumableMappingAdded,
|
||||
id: giftId,
|
||||
toAccountId: id,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
{ "Id", giftId },
|
||||
{ "ConsumableType", "JfnVXFmilU6ysv-VbTAe3A" },
|
||||
{ "PlatformMask", 4294967295 },
|
||||
{ "Count", 1 },
|
||||
{ "InitialCount", 1 },
|
||||
{ "UnlockedLevel", 0 },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "IsActive", false },
|
||||
{ "ActiveDurationMinutes", null },
|
||||
{ "Category", 0 },
|
||||
{ "IsPlatformLocked", false }
|
||||
}
|
||||
);
|
||||
await notificationService.SendNotificationToPlayer(id, consumableNotification);
|
||||
}
|
||||
}
|
||||
catch (Exception notifEx)
|
||||
{
|
||||
// Log the error but don't fail the gift consumption
|
||||
Console.WriteLine($"Error sending notification: {notifEx.Message}");
|
||||
}
|
||||
|
||||
return Results.Ok(new { success = true });
|
||||
return Results.Ok(RecNetResult.Ok());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1855,4 +1825,58 @@ public class APIController
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static readonly string ImagesDir;
|
||||
static APIController()
|
||||
{
|
||||
var dir = "Images";
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
ImagesDir = dir;
|
||||
}
|
||||
|
||||
private void MapImageEndpoints(WebApplication app)
|
||||
{
|
||||
app.MapPost("/api/images/v4/uploadsaved", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var form = await context.Request.ReadFormAsync();
|
||||
var file = form.Files.FirstOrDefault();
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return Results.BadRequest(new { error = "No file found in request" });
|
||||
}
|
||||
|
||||
var imageId = Guid.NewGuid().ToString("N");
|
||||
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||
|
||||
var validExtensions = new[] { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp" };
|
||||
if (string.IsNullOrEmpty(extension) || !validExtensions.Contains(extension))
|
||||
{
|
||||
extension = ".png";
|
||||
}
|
||||
|
||||
var savedFileName = imageId + extension;
|
||||
var filePath = Path.Combine(ImagesDir, savedFileName);
|
||||
|
||||
using (var fileStream = File.Create(filePath))
|
||||
{
|
||||
await file.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
ImageName = savedFileName
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Error uploading image: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,5 +453,61 @@ public class AccountsController
|
||||
|
||||
return Results.Json(new { success = true });
|
||||
});
|
||||
|
||||
app.MapPut("/account/me/profileimage", async (HttpRequest request, AppDbContext db) =>
|
||||
{
|
||||
var authHeader = request.Headers.Authorization.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
return Results.Unauthorized();
|
||||
|
||||
var token = authHeader.Substring("Bearer ".Length);
|
||||
var accountId = jwtService.ValidateAndGetAccountId(token);
|
||||
|
||||
if (string.IsNullOrEmpty(accountId))
|
||||
return Results.Unauthorized();
|
||||
|
||||
if (!int.TryParse(accountId.AsSpan(), out var id))
|
||||
return Results.Unauthorized();
|
||||
|
||||
string newProfileImage = "";
|
||||
|
||||
if (request.ContentLength.HasValue && request.ContentLength > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.EnableBuffering();
|
||||
using var reader = new StreamReader(request.Body, leaveOpen: true);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
foreach (var pair in body.Split('&'))
|
||||
{
|
||||
var keyValue = pair.Split('=');
|
||||
if (keyValue.Length == 2)
|
||||
{
|
||||
var key = Uri.UnescapeDataString(keyValue[0]);
|
||||
var value = Uri.UnescapeDataString(keyValue[1]);
|
||||
|
||||
if (key == "imageName")
|
||||
newProfileImage = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
request.Body.Position = 0;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var account = await db.Accounts.FindAsync(id);
|
||||
if (account == null)
|
||||
return Results.NotFound();
|
||||
|
||||
account.ProfileImage = newProfileImage;
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Results.Ok(RecNetResult.Ok());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,50 @@ public class CDNController
|
||||
var json = File.ReadAllText("JSON/loadingscreentipdata.json");
|
||||
return Results.Content(json, "application/json");
|
||||
});
|
||||
|
||||
app.MapPost("/upload", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var form = await context.Request.ReadFormAsync();
|
||||
var file = form.Files.FirstOrDefault();
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return Results.BadRequest(new { error = "No file found in request" });
|
||||
}
|
||||
|
||||
var imageId = Guid.NewGuid().ToString("N");
|
||||
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||
|
||||
var validExtensions = new[] { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp" };
|
||||
if (string.IsNullOrEmpty(extension) || !validExtensions.Contains(extension))
|
||||
{
|
||||
extension = ".png";
|
||||
}
|
||||
|
||||
var savedFileName = imageId + extension;
|
||||
var filePath = Path.Combine("Images", savedFileName);
|
||||
|
||||
if (!Directory.Exists("Images"))
|
||||
{
|
||||
Directory.CreateDirectory("Images");
|
||||
}
|
||||
|
||||
using (var fileStream = File.Create(filePath))
|
||||
{
|
||||
await file.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
filename = savedFileName
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Error uploading image: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,54 @@ public class ImageController
|
||||
|
||||
public void MapEndpoints(WebApplication app)
|
||||
{
|
||||
var imagesDir = "Images";
|
||||
if (!Directory.Exists(imagesDir))
|
||||
{
|
||||
Directory.CreateDirectory(imagesDir);
|
||||
}
|
||||
|
||||
app.MapGet("/{imageId}", (string imageId, HttpContext context) =>
|
||||
{
|
||||
var imagesDir = "Images";
|
||||
if (!Directory.Exists(imagesDir))
|
||||
{
|
||||
context.Response.StatusCode = 404;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(imagesDir, $"{imageId}.*");
|
||||
var filePath = files.FirstOrDefault();
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
context.Response.StatusCode = 404;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
var contentType = extension switch
|
||||
{
|
||||
".png" => "image/png",
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".gif" => "image/gif",
|
||||
".webp" => "image/webp",
|
||||
".bmp" => "image/bmp",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
|
||||
context.Response.ContentType = contentType;
|
||||
return File.OpenRead(filePath).CopyToAsync(context.Response.Body);
|
||||
});
|
||||
|
||||
app.MapGet("/", (HttpContext context) =>
|
||||
{
|
||||
var imageId = context.Request.Query["imageId"].FirstOrDefault();
|
||||
var sig = context.Request.Query["sig"].FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrEmpty(imageId))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(imagesDir))
|
||||
{
|
||||
@@ -41,5 +86,47 @@ public class ImageController
|
||||
context.Response.ContentType = contentType;
|
||||
return File.OpenRead(filePath).CopyToAsync(context.Response.Body);
|
||||
});
|
||||
|
||||
app.MapPost("/api/images/v4/uploadsaved", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var form = await context.Request.ReadFormAsync();
|
||||
var file = form.Files.FirstOrDefault();
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return Results.BadRequest(new { error = "No file found in request" });
|
||||
}
|
||||
|
||||
var imageId = Guid.NewGuid().ToString("N");
|
||||
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||
|
||||
var validExtensions = new[] { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp" };
|
||||
if (string.IsNullOrEmpty(extension) || !validExtensions.Contains(extension))
|
||||
{
|
||||
extension = ".png";
|
||||
}
|
||||
|
||||
var savedFileName = imageId + extension;
|
||||
var filePath = Path.Combine(imagesDir, savedFileName);
|
||||
|
||||
using (var fileStream = File.Create(filePath))
|
||||
{
|
||||
await file.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
imageId = imageId,
|
||||
imageUrl = $"/{imageId}{extension}",
|
||||
success = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Error uploading image: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -96,12 +96,10 @@ public class MatchmakingController
|
||||
RoomInstance? instanceToUse;
|
||||
string photonRoomId;
|
||||
|
||||
// Check if player already has an instance in this room
|
||||
var existingInstance = await db.RoomInstances.FirstOrDefaultAsync(r => r.OwnerAccountId == id && r.roomId == roomData.RoomId);
|
||||
|
||||
if (existingInstance != null)
|
||||
{
|
||||
// Player already has an instance in this room, update it
|
||||
photonRoomId = existingInstance.photonRoomId ?? Guid.NewGuid().ToString();
|
||||
existingInstance.roomInstanceId = existingInstance.Id;
|
||||
existingInstance.roomId = roomData.RoomId;
|
||||
@@ -156,7 +154,7 @@ public class MatchmakingController
|
||||
name = roomData.Name,
|
||||
maxCapacity = maxCapacity,
|
||||
isFull = false,
|
||||
isPrivate = roomData.IsDorm || roomData.RoomId == 1, // Private if it's a dorm or special room
|
||||
isPrivate = roomData.IsDorm || roomData.RoomId == 1,
|
||||
isInProgress = false,
|
||||
EncryptVoiceChat = roomData.EncryptVoiceChat
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using CannedNet.Hubs;
|
||||
using CannedNet.Services.Infrastructure;
|
||||
|
||||
namespace CannedNet.Services.Controllers;
|
||||
@@ -9,16 +8,11 @@ public class NSController
|
||||
|
||||
public void MapEndpoints(WebApplication app)
|
||||
{
|
||||
// Rec Room automatically calls this and sets the endpoints that correspond to services in the JSON file. These will be moved to the main server config once I get there.
|
||||
app.MapGet("/", () =>
|
||||
{
|
||||
var json = File.ReadAllText("JSON/endpoints.json");
|
||||
return Results.Content(json, "application/json");
|
||||
});
|
||||
|
||||
app.MapGet("/photon", () =>
|
||||
{
|
||||
var json = File.ReadAllText("JSON/photonsettings.json");
|
||||
return Results.Content(json, "application/json");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ public class JwtTokenService
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim("iss", "https://rr.lapis.codes"),
|
||||
new Claim("iss", "https://api.lapis.codes"),
|
||||
new Claim("nbf", ((DateTimeOffset)now).ToUnixTimeSeconds().ToString()),
|
||||
new Claim("iat", ((DateTimeOffset)now).ToUnixTimeSeconds().ToString()),
|
||||
new Claim("exp", ((DateTimeOffset)exp).ToUnixTimeSeconds().ToString()),
|
||||
new Claim("aud", "https://rr.lapis.codes/resources"),
|
||||
new Claim("aud", "https://api.lapis.codes/resources"),
|
||||
new Claim("amr", "cached_login"),
|
||||
new Claim("client_id", "recroom"),
|
||||
new Claim("sub", accountId),
|
||||
@@ -43,6 +43,8 @@ public class JwtTokenService
|
||||
new Claim("rn.ver", "20210129"),
|
||||
new Claim("rn.plat", "0"),
|
||||
new Claim("role", "gameClient"),
|
||||
new Claim("role", "developer"),
|
||||
new Claim("role", "moderator"),
|
||||
new Claim("jti", Guid.NewGuid().ToString("N").Substring(0, 32).ToUpper())
|
||||
};
|
||||
|
||||
|
||||
@@ -205,55 +205,41 @@ public class NotificationService
|
||||
|
||||
private static async Task SendToConnection(IHubContext<NotificationsHub> hubContext, string connectionId, NotificationMessage notification)
|
||||
{
|
||||
var notificationData = BuildNotificationObject(notification);
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(notificationData);
|
||||
System.Console.WriteLine($"[NotificationService] Sending notification to {connectionId}: {json}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine($"[NotificationService] Error serializing notification: {ex.Message}");
|
||||
}
|
||||
await hubContext.Clients.Client(connectionId).SendAsync("Notification", notificationData);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> BuildNotificationObject(NotificationMessage notification)
|
||||
{
|
||||
var dict = new Dictionary<string, object?>
|
||||
{
|
||||
{ "notificationId", notification.NotificationId ?? "" },
|
||||
{ "notificationType", (int?)notification.NotificationType ?? 0 },
|
||||
};
|
||||
|
||||
// Only add optional fields if they have values
|
||||
if (notification.Id.HasValue)
|
||||
dict.Add("id", notification.Id.Value);
|
||||
if (notification.FromAccountId.HasValue)
|
||||
dict.Add("fromAccountId", notification.FromAccountId.Value);
|
||||
if (notification.ToAccountId.HasValue)
|
||||
dict.Add("toAccountId", notification.ToAccountId.Value);
|
||||
if (notification.CreatedAt.HasValue)
|
||||
dict.Add("createdAt", notification.CreatedAt.Value.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
|
||||
|
||||
// Properly serialize the data dictionary, converting DateTime objects to ISO 8601 strings
|
||||
var serializedData = new Dictionary<string, object>();
|
||||
if (notification.Data != null && notification.Data.Count > 0)
|
||||
var msgData = new Dictionary<string, object>();
|
||||
if (notification.Data != null)
|
||||
{
|
||||
foreach (var kvp in notification.Data)
|
||||
{
|
||||
if (kvp.Value == null)
|
||||
continue;
|
||||
|
||||
if (kvp.Value is DateTime dt)
|
||||
{
|
||||
serializedData[kvp.Key] = dt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
|
||||
msgData[kvp.Key] = dt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
|
||||
}
|
||||
else if (kvp.Value != null)
|
||||
else
|
||||
{
|
||||
serializedData[kvp.Key] = kvp.Value;
|
||||
msgData[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dict.Add("data", serializedData);
|
||||
return dict;
|
||||
var notificationData = new
|
||||
{
|
||||
Id = (int?)notification.NotificationType ?? 0,
|
||||
Msg = msgData
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(notificationData);
|
||||
Console.WriteLine($"[NotificationService] Sending notification to {connectionId}: {json}");
|
||||
|
||||
await hubContext.Clients.Client(connectionId).SendAsync("Notification", json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NotificationService] Error sending notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user