a few changes

- fixes nullrefs on notifications
This commit is contained in:
Lapis
2026-04-10 18:39:35 -04:00
parent 5bfae5d3c1
commit bb5b49b6fe
10 changed files with 291 additions and 98 deletions
+1
View File
@@ -50,3 +50,4 @@ public class NotificationsHub : Hub
return Context.GetHttpContext()?.RequestServices.GetService(typeof(T)) as T;
}
}
+1 -1
View File
@@ -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",
+65 -41
View File
@@ -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");
});
}
}
+4 -2
View File
@@ -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())
};
+23 -37
View File
@@ -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}");
}
}
}