Few more migrations

This commit is contained in:
notabird
2026-06-07 11:25:49 -07:00
parent a1097c4070
commit f4fe623e24
7 changed files with 637 additions and 645 deletions
+104 -41
View File
@@ -1,48 +1,111 @@
using CannedNet;
using CannedNet.Data;
using CannedNet.Hubs;
using CannedNet.Services;
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
var apps = new List<(WebApplication App, ServiceRegistry Service)>();
namespace CannedNet;
foreach (var service in Services.All)
public static class Program
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddLogging(logging =>
{
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Debug);
});
service.ConfigureBuilder(builder);
builder.WebHost.UseUrls($"http://*:{service.Port}");
builder.Configuration.AddJsonFile($"AppConfigs/appsettings.{service.Name}.json", optional: true, reloadOnChange: true);
apps.Add((builder.Build(), service));
public static async Task Main(string[] args) {
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
//builder.Services.AddLogging(logging => {
// logging.AddConsole();
// logging.SetMinimumLevel(LogLevel.Debug);
//});
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres";
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.PropertyNamingPolicy = null);
builder.Services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connectionString));
builder.Services.AddSingleton<NotificationService>();
builder.Services.AddScoped<StorefrontFillService>();
builder.Services.AddScoped<JwtTokenService>();
builder.Services.AddSignalR();
WebApplication app = builder.Build();
app.MapHub<NotificationsHub>("/hub/v1");
app.UseHttpsRedirection();
//app.UseRequestLogging();
app.MapControllers();
IHubContext<NotificationsHub> hubContext = app.Services.GetRequiredService<IHubContext<NotificationsHub>>();
NotificationService.SetHubContext(hubContext);
//if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json", "CannedNet v1");
options.RoutePrefix = "swagger/ui/";
});
//}
using IServiceScope scope = app.Services.CreateScope();
AppDbContext db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
StorefrontFillService seedingService = scope.ServiceProvider.GetRequiredService<StorefrontFillService>();
await seedingService.FillStorefrontsAsync();
try {
await db.Database.MigrateAsync();
} catch {
await db.Database.EnsureCreatedAsync();
}
await app.RunAsync();
}
//public static async Task T() {
// List<(WebApplication App, ServiceRegistry Service)> apps = new();
//
// foreach (var service in Services.Infrastructure.Services.All)
// {
// var builder = WebApplication.CreateBuilder();
//
// builder.Services.AddLogging(logging =>
// {
// logging.AddConsole();
// logging.SetMinimumLevel(LogLevel.Debug);
// });
//
// service.ConfigureBuilder(builder);
// builder.WebHost.UseUrls($"http://*:{service.Port}");
// builder.Configuration.AddJsonFile($"AppConfigs/appsettings.{service.Name}.json", optional: true, reloadOnChange: true);
//
// apps.Add((builder.Build(), service));
// }
//
// using var scope = apps[0].App.Services.CreateScope();
// var dbContext = scope.ServiceProvider.GetRequiredService<CannedNet.Data.AppDbContext>();
// try
// {
// dbContext.Database.Migrate();
// }
// catch (Exception ex)
// {
// dbContext.Database.EnsureCreated();
// }
//
// // automatically fill out storefronts tables from the JSON's
// var seedingService = scope.ServiceProvider.GetRequiredService<StorefrontFillService>();
// await seedingService.FillStorefrontsAsync();
//
// var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>();
//
// foreach (var (app, service) in apps)
// {
// service.MapEndpoints(app, jwtService);
// app.UseRequestLogging();
// }
//
// await Task.WhenAll(apps.Select(t => t.App.RunAsync()));
//}
}
using var scope = apps[0].App.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<CannedNet.Data.AppDbContext>();
try
{
dbContext.Database.Migrate();
}
catch (Exception ex)
{
dbContext.Database.EnsureCreated();
}
// automatically fill out storefronts tables from the JSON's
var seedingService = scope.ServiceProvider.GetRequiredService<StorefrontFillService>();
await seedingService.FillStorefrontsAsync();
var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>();
foreach (var (app, service) in apps)
{
service.MapEndpoints(app, jwtService);
app.UseRequestLogging();
}
await Task.WhenAll(apps.Select(t => t.App.RunAsync()));
@@ -1,512 +1,458 @@
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using CannedNet.Services;
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace CannedNet.Services.Controllers;
public class AccountsController
//TODO: remove duplicate code(most of it is checking if the user is authenticated, ASP.NET has stuff for this)
[ApiController, Route("account")]
public class AccountsController : ControllerBase
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
//[HttpGet]
//public Task<IResult> Root() {
// return Task.FromResult(Results.Ok("FUCKL MYSJDIOJD"));
//}
public void MapEndpoints(WebApplication app, JwtTokenService jwtService)
{
app.MapGet("/", (HttpRequest request) =>
{
return Results.Ok("FUCKL MYSJDIOJD");
});
app.MapGet("/account/me", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
[HttpGet("me")]
public async Task<IResult> Me(HttpRequest request, AppDbContext db, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out int id))
return Results.Unauthorized();
var account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
Account? account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
var selfAccount = new SelfAccount
{
AccountId = id,
ProfileImage = account.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{id}",
DisplayName = account.DisplayName ?? $"Player{id}",
CreatedAt = account.CreatedAt,
Email = null,
Phone = null,
JuniorState = null,
Birthday = null,
ParentAccountId = null,
AvailableUsernameChanges = 1
};
SelfAccount selfAccount = new SelfAccount {
AccountId = id,
ProfileImage = account.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{id}",
DisplayName = account.DisplayName ?? $"Player{id}",
CreatedAt = account.CreatedAt,
Email = null,
Phone = null,
JuniorState = null,
Birthday = null,
ParentAccountId = null,
AvailableUsernameChanges = 1
};
return Results.Ok(selfAccount);
});
app.MapGet("/account/bulk", async (HttpRequest request, AppDbContext db) =>
{
var ids = request.Query["id"];
var accountIds = new List<int>();
foreach (var id in ids)
{
if (int.TryParse(id, out var accountId))
{
accountIds.Add(accountId);
}
}
var accounts = await db.Accounts
.Where(a => accountIds.Contains(a.AccountId.Value))
.ToListAsync();
var result = accounts.Select(a => new Account
{
AccountId = a.AccountId,
ProfileImage = a.ProfileImage ?? "DefaultProfileImage.jpg",
IsJunior = a.IsJunior,
Platforms = a.Platforms ?? 0,
PersonalPronouns = a.PersonalPronouns ?? 0,
IdentityFlags = a.IdentityFlags ?? 0,
Username = a.Username ?? $"Player{a.AccountId}",
DisplayName = a.DisplayName ?? $"Player{a.AccountId}",
CreatedAt = a.CreatedAt
}).ToList();
return Results.Json(result);
});
app.MapGet("/account/{id}", async (HttpRequest request, string id, AppDbContext db) =>
{
if (!int.TryParse(id, out var accountId))
return Results.BadRequest();
var account = await db.Accounts.FindAsync(accountId);
if (account == null)
return Results.NotFound();
var result = new Account
{
AccountId = account.AccountId,
ProfileImage = account.ProfileImage ?? "DefaultProfileImage.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{account.AccountId}",
DisplayName = account.DisplayName ?? $"Player{account.AccountId}",
CreatedAt = account.CreatedAt
};
return Results.Json(result);
});
app.MapPost("/account/create", async (HttpRequest httpRequest, AppDbContext db) =>
{
int platform = 0;
string platformId = "";
if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0)
{
try
{
var contentType = httpRequest.ContentType ?? "";
httpRequest.EnableBuffering();
using var reader = new StreamReader(httpRequest.Body, leaveOpen: true);
var body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body) && contentType.Contains("application/x-www-form-urlencoded"))
{
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 == "platform" && int.TryParse(value, out var parsedPlatform))
platform = parsedPlatform;
else if (key == "platformId")
platformId = value;
}
}
}
httpRequest.Body.Position = 0;
}
catch { }
}
var accountId = new Random().Next(10000, 99999);
var account = new Account
{
AccountId = accountId,
ProfileImage = "DefaultProfileImage.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
};
db.Accounts.Add(account);
if (!string.IsNullOrEmpty(platformId))
{
db.CachedLogins.Add(new CachedLogin
{
AccountId = accountId,
Platform = (PlatformType)platform,
PlatformID = platformId,
LastLoginTime = DateTime.UtcNow,
RequirePassword = false
});
}
await db.SaveChangesAsync();
// create players dorm room
var maxRoomId = await db.Rooms.MaxAsync(r => (int?)r.RoomId) ?? 0;
var maxId = await db.Rooms.MaxAsync(r => (int?)r.Id) ?? 0;
var dormRoomId = maxRoomId + 1;
var dormId = maxId + 1;
var dormRoom = new Room
{
Id = maxId + 1,
RoomId = dormRoomId,
Name = "DormRoom",
Description = "Your personal room",
CreatorAccountId = accountId,
ImageName = "",
State = 0,
Accessibility = 0,
SupportsLevelVoting = false,
IsRRO = false,
IsDorm = true,
CloningAllowed = false,
SupportsVRLow = true,
SupportsQuest2 = true,
SupportsMobile = true,
SupportsScreens = true,
SupportsWalkVR = true,
SupportsTeleportVR = true,
SupportsJuniors = true,
MinLevel = 0,
WarningMask = 0,
CustomWarning = null,
DisableMicAutoMute = false,
DisableRoomComments = false,
EncryptVoiceChat = false,
CreatedAt = DateTime.UtcNow,
Tags = "[]"
};
db.Rooms.Add(dormRoom);
// Create a sub room for the dorm
var dormSubRoom = new SubRoom
{
RoomId = dormRoomId,
SubRoomId = 1,
Name = "DormRoom",
DataBlob = "",
IsSandbox = false,
MaxPlayers = 4,
Accessibility = 0,
UnitySceneId = "76d98498-60a1-430c-ab76-b54a29b7a163", // Dorm scene ID
DataSavedAt = DateTime.UtcNow
};
db.SubRooms.Add(dormSubRoom);
await db.SaveChangesAsync();
return Results.Ok(RecNetResultWithValue<Account>.Ok(account));
});
app.MapGet("/parentalcontrol/me", 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();
return Results.Content($"{{\"accountId\":{accountId},\"disallowInAppPurchases\":false}}", "application/json");
});
app.MapPut("/account/me/displayname", 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 newDisplayName = "";
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 == "displayName")
newDisplayName = value;
}
}
}
request.Body.Position = 0;
}
catch { }
}
var account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.DisplayName = newDisplayName;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
});
app.MapPut("/account/me/username", 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 newAccountName = "";
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 == "username")
newAccountName = value;
}
}
}
request.Body.Position = 0;
}
catch { }
}
var account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.Username = newAccountName;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
});
app.MapGet("/account/{id}/bio", async (HttpRequest request, string id, AppDbContext db) =>
{
if (!int.TryParse(id, out var accountId))
return Results.BadRequest();
var bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == accountId);
if (bio == null)
{
return Results.Json(new { accountId = accountId, bio = "" });
}
return Results.Json(new { accountId = bio.accountId, bio = bio.bio });
});
app.MapPut("/account/me/bio", 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 newBio = "";
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 == "bio")
newBio = Uri.UnescapeDataString(value);
}
}
}
request.Body.Position = 0;
}
catch { }
}
var bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == id);
if (bio == null)
{
bio = new PlayerBio
{
accountId = id,
bio = newBio
};
db.PlayerBios.Add(bio);
}
else
{
bio.bio = newBio;
db.PlayerBios.Update(bio);
}
await db.SaveChangesAsync();
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());
});
return Results.Ok(selfAccount);
}
}
[HttpGet("bulk")]
public async Task<IResult> Bulk(HttpRequest request, AppDbContext db) {
StringValues ids = request.Query["id"];
List<int> accountIds = [];
foreach (string? id in ids) {
if (int.TryParse(id, out int accountId)) {
accountIds.Add(accountId);
}
}
List<Account> accounts = await db.Accounts
.Where(a => accountIds.Contains(a.AccountId.Value))
.ToListAsync();
List<Account> result = accounts.Select(a => new Account {
AccountId = a.AccountId,
ProfileImage = a.ProfileImage ?? "DefaultProfileImage.jpg",
IsJunior = a.IsJunior,
Platforms = a.Platforms ?? 0,
PersonalPronouns = a.PersonalPronouns ?? 0,
IdentityFlags = a.IdentityFlags ?? 0,
Username = a.Username ?? $"Player{a.AccountId}",
DisplayName = a.DisplayName ?? $"Player{a.AccountId}",
CreatedAt = a.CreatedAt
}).ToList();
return Results.Json(result);
}
[HttpGet("{id}")]
public async Task<IResult> Id(HttpRequest request, string id, AppDbContext db) {
if (!int.TryParse(id, out int accountId))
return Results.BadRequest();
Account? account = await db.Accounts.FindAsync(accountId);
if (account == null)
return Results.NotFound();
Account result = new() {
AccountId = account.AccountId,
ProfileImage = account.ProfileImage ?? "DefaultProfileImage.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{account.AccountId}",
DisplayName = account.DisplayName ?? $"Player{account.AccountId}",
CreatedAt = account.CreatedAt
};
return Results.Json(result);
}
[HttpPost("cerate")]
public async Task<IResult> Create(HttpRequest httpRequest, AppDbContext db) {
int platform = 0;
string platformId = "";
if (httpRequest.ContentLength is > 0) {
try {
string contentType = httpRequest.ContentType ?? "";
httpRequest.EnableBuffering();
using StreamReader reader = new(httpRequest.Body, leaveOpen: true);
string body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body) && contentType.Contains("application/x-www-form-urlencoded")) {
foreach (string pair in body.Split('&')) {
string[] keyValue = pair.Split('=');
if (keyValue.Length != 2) continue;
string key = Uri.UnescapeDataString(keyValue[0]);
string value = Uri.UnescapeDataString(keyValue[1]);
switch (key) {
case "platform" when int.TryParse(value, out var parsedPlatform):
platform = parsedPlatform;
break;
case "platformId":
platformId = value;
break;
}
}
}
httpRequest.Body.Position = 0;
}
catch { }
}
int accountId = new Random().Next(10000, 99999);
Account account = new() {
AccountId = accountId,
ProfileImage = "DefaultProfileImage.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
};
db.Accounts.Add(account);
if (!string.IsNullOrEmpty(platformId)) {
db.CachedLogins.Add(new CachedLogin {
AccountId = accountId,
Platform = (PlatformType)platform,
PlatformID = platformId,
LastLoginTime = DateTime.UtcNow,
RequirePassword = false
});
}
await db.SaveChangesAsync();
// create players dorm room
int maxRoomId = await db.Rooms.MaxAsync(r => (int?)r.RoomId) ?? 0;
int maxId = await db.Rooms.MaxAsync(r => (int?)r.Id) ?? 0;
int dormRoomId = maxRoomId + 1;
//int dormId = maxId + 1;
Room dormRoom = new Room {
Id = maxId + 1,
RoomId = dormRoomId,
Name = "DormRoom",
Description = "Your personal room",
CreatorAccountId = accountId,
ImageName = "",
State = 0,
Accessibility = 0,
SupportsLevelVoting = false,
IsRRO = false,
IsDorm = true,
CloningAllowed = false,
SupportsVRLow = true,
SupportsQuest2 = true,
SupportsMobile = true,
SupportsScreens = true,
SupportsWalkVR = true,
SupportsTeleportVR = true,
SupportsJuniors = true,
MinLevel = 0,
WarningMask = 0,
CustomWarning = null,
DisableMicAutoMute = false,
DisableRoomComments = false,
EncryptVoiceChat = false,
CreatedAt = DateTime.UtcNow,
Tags = "[]"
};
db.Rooms.Add(dormRoom);
// Create a sub room for the dorm
SubRoom dormSubRoom = new SubRoom {
RoomId = dormRoomId,
SubRoomId = 1,
Name = "DormRoom",
DataBlob = "",
IsSandbox = false,
MaxPlayers = 4,
Accessibility = 0,
UnitySceneId = "76d98498-60a1-430c-ab76-b54a29b7a163", // Dorm scene ID
DataSavedAt = DateTime.UtcNow
};
db.SubRooms.Add(dormSubRoom);
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok(account));
}
[HttpPut("me/displayname")]
public async Task<IResult> PutMeDisplayName(HttpRequest request, AppDbContext db, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
string newDisplayName = "";
if (request.ContentLength is > 0) {
try {
request.EnableBuffering();
using StreamReader reader = new(request.Body, leaveOpen: true);
string body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body)) {
foreach (string pair in body.Split('&')) {
string[] keyValue = pair.Split('=');
if (keyValue.Length != 2) continue;
string key = Uri.UnescapeDataString(keyValue[0]);
string value = Uri.UnescapeDataString(keyValue[1]);
if (key == "displayName")
newDisplayName = value;
}
}
request.Body.Position = 0;
}
catch { }
}
Account? account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.DisplayName = newDisplayName;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
}
[HttpPut("me/username")]
public async Task<IResult> PutMeUsername(HttpRequest request, AppDbContext db, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) ||
!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out int id))
return Results.Unauthorized();
string newAccountName = "";
if (request.ContentLength is > 0) {
try {
request.EnableBuffering();
using StreamReader reader = new(request.Body, leaveOpen: true);
string body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body)) {
foreach (string pair in body.Split('&')) {
string[] keyValue = pair.Split('=');
if (keyValue.Length != 2) continue;
string key = Uri.UnescapeDataString(keyValue[0]);
string value = Uri.UnescapeDataString(keyValue[1]);
if (key == "username")
newAccountName = value;
}
}
request.Body.Position = 0;
}
catch { }
}
Account? account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.Username = newAccountName;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
}
[HttpGet("{id}/bio")]
public async Task<IResult> GetBio(string id, AppDbContext db) {
if (!int.TryParse(id, out int accountId))
return Results.BadRequest();
PlayerBio? bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == accountId);
return Results.Json(bio == null
? new { accountId = accountId, bio = "" }
: new { accountId = bio.accountId, bio = bio.bio });
}
[HttpPut("me/bio")]
public async Task<IResult> PutMeBio(HttpRequest request, AppDbContext db, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) ||
!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out int id))
return Results.Unauthorized();
string newBio = "";
if (request.ContentLength is > 0) {
try {
request.EnableBuffering();
using StreamReader reader = new StreamReader(request.Body, leaveOpen: true);
string body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body)) {
foreach (string pair in body.Split('&')) {
string[] keyValue = pair.Split('=');
if (keyValue.Length != 2) continue;
string key = Uri.UnescapeDataString(keyValue[0]);
string value = Uri.UnescapeDataString(keyValue[1]);
if (key == "bio")
newBio = Uri.UnescapeDataString(value);
}
}
request.Body.Position = 0;
}
catch { }
}
PlayerBio? bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == id);
if (bio == null) {
bio = new PlayerBio {
accountId = id,
bio = newBio
};
db.PlayerBios.Add(bio);
}
else {
bio.bio = newBio;
db.PlayerBios.Update(bio);
}
await db.SaveChangesAsync();
return Results.Json(new { success = true });
}
[HttpPut("me/profileimage")]
public async Task<IResult> PutMeProfileImage(HttpRequest request, AppDbContext db, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) ||
!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out int id))
return Results.Unauthorized();
string newProfileImage = "";
if (request.ContentLength is > 0) {
try {
request.EnableBuffering();
using StreamReader reader = new(request.Body, leaveOpen: true);
string body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body)) {
foreach (var pair in body.Split('&')) {
string[] keyValue = pair.Split('=');
if (keyValue.Length != 2) continue;
string key = Uri.UnescapeDataString(keyValue[0]);
string value = Uri.UnescapeDataString(keyValue[1]);
if (key == "imageName")
newProfileImage = value;
}
}
request.Body.Position = 0;
}
catch { }
}
Account? account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.ProfileImage = newProfileImage;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
}
[HttpGet("parentalcontrol/me")]
public async Task<IResult> ParentalControl(HttpRequest request, JwtTokenService jwtService) {
string authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) ||
!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
string token = authHeader.Substring("Bearer ".Length);
string? accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out _))
return Results.Unauthorized();
return Results.Content($"{{\"accountId\":{accountId},\"disallowInAppPurchases\":false}}", "application/json");
}
}
@@ -1,13 +1,13 @@
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.Mvc;
namespace CannedNet.Services.Controllers;
public class ChatController
[ApiController]
public class ChatController : ControllerBase
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
app.MapGet("/thread", () => Results.Content("[]", "application/json"));
[HttpGet("thread")]
public async Task<IResult> Get() {
return Results.Content("[]", "application/json");
}
}
@@ -1,5 +1,5 @@
using System.Net.Mime;
using CannedNet.Services.Infrastructure;
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
@@ -7,104 +7,91 @@ using SixLabors.ImageSharp.Processing;
namespace CannedNet.Services.Controllers;
public class ImageController
[ApiController, Route("")]
public class ImageController : ControllerBase
{
private static readonly byte[] PlaceholderJpeg;
static ImageController()
{
static ImageController() {
Signatures.Init();
using var image = new Image<Rgba32>(64, 64);
using Image<Rgba32> image = new(64, 64);
image.Mutate(x => x.BackgroundColor(Color.FromRgb(32, 32, 32)));
using var ms = new MemoryStream();
using MemoryStream ms = new();
image.SaveAsJpeg(ms, new JpegEncoder { Quality = 50 });
PlaceholderJpeg = ms.ToArray();
}
public WebApplicationBuilder Initialize(string[]? args = null)
=> ServiceExtensions.CreateRecNetBuilder(args);
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
var imagesDir = Path.Combine("Images");
[HttpGet("{imageName}")]
public async Task<IResult> GetImage(HttpContext context, string imageName) {
string imagesDir = Path.Combine("Images");
Directory.CreateDirectory(imagesDir);
string filePath = Path.Combine(imagesDir, imageName);
app.MapGet("/{imageName}", async (HttpContext context, string imageName) =>
{
var filePath = Path.Combine(imagesDir, imageName);
byte[] imageBytes;
string contentType;
byte[] imageBytes;
string contentType;
if (System.IO.File.Exists(filePath)) {
imageBytes = await System.IO.File.ReadAllBytesAsync(filePath);
string ext = Path.GetExtension(imageName).ToLowerInvariant();
contentType = ext switch {
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".webp" => "image/webp",
".bmp" => "image/bmp",
_ => "image/png"
};
}
else {
imageBytes = PlaceholderJpeg;
contentType = "image/jpeg";
}
if (File.Exists(filePath))
{
imageBytes = await File.ReadAllBytesAsync(filePath);
var ext = Path.GetExtension(imageName).ToLowerInvariant();
contentType = ext switch
{
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".webp" => "image/webp",
".bmp" => "image/bmp",
_ => "image/png"
};
}
else
{
imageBytes = PlaceholderJpeg;
contentType = "image/jpeg";
}
string? cropSquare = context.Request.Query["cropSquare"].FirstOrDefault();
string? widthStr = context.Request.Query["width"].FirstOrDefault();
string? heightStr = context.Request.Query["height"].FirstOrDefault();
var cropSquare = context.Request.Query["cropSquare"].FirstOrDefault();
var widthStr = context.Request.Query["width"].FirstOrDefault();
var heightStr = context.Request.Query["height"].FirstOrDefault();
if (string.IsNullOrEmpty(widthStr) && string.IsNullOrEmpty(heightStr) && string.IsNullOrEmpty(cropSquare))
{
SignImageResponse(context, ref imageBytes);
return Results.File(imageBytes, contentType);
}
using var image = Image.Load(imageBytes);
var resizeWidth = 0;
var resizeHeight = 0;
if (!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false")
{
var size = Math.Min(image.Width, image.Height);
var x = (image.Width - size) / 2;
var y = (image.Height - size) / 2;
image.Mutate(img => img.Crop(new Rectangle(x, y, size, size)));
}
if (int.TryParse(widthStr, out var w))
resizeWidth = w;
if (int.TryParse(heightStr, out var h))
resizeHeight = h;
if (resizeWidth > 0 || resizeHeight > 0)
{
image.Mutate(x => x.Resize(resizeWidth, resizeHeight));
}
using var output = new MemoryStream();
await image.SaveAsJpegAsync(output, new JpegEncoder { Quality = 85 });
imageBytes = output.ToArray();
if (string.IsNullOrEmpty(widthStr) && string.IsNullOrEmpty(heightStr) && string.IsNullOrEmpty(cropSquare)) {
SignImageResponse(context, ref imageBytes);
return Results.File(imageBytes, "image/jpeg");
});
return Results.File(imageBytes, contentType);
}
using Image image = Image.Load(imageBytes);
int resizeWidth = 0;
int resizeHeight = 0;
if (!string.IsNullOrEmpty(cropSquare) && cropSquare != "0" && cropSquare != "false") {
int size = Math.Min(image.Width, image.Height);
int x = (image.Width - size) / 2;
int y = (image.Height - size) / 2;
image.Mutate(img => img.Crop(new Rectangle(x, y, size, size)));
}
if (int.TryParse(widthStr, out int w))
resizeWidth = w;
if (int.TryParse(heightStr, out int h))
resizeHeight = h;
if (resizeWidth > 0 || resizeHeight > 0) {
image.Mutate(x => x.Resize(resizeWidth, resizeHeight));
}
using MemoryStream output = new();
await image.SaveAsJpegAsync(output, new JpegEncoder { Quality = 85 });
imageBytes = output.ToArray();
SignImageResponse(context, ref imageBytes);
return Results.File(imageBytes, "image/jpeg");
}
private static void SignImageResponse(HttpContext context, ref byte[] imageBytes)
{
private static void SignImageResponse(HttpContext context, ref byte[] imageBytes) {
if (context.Request.Query["sig"] != "p1") return;
var signature = Signatures.Sign(imageBytes);
if (signature != null)
{
string? signature = Signatures.Sign(imageBytes);
if (signature != null) {
context.Response.Headers["Content-Signature"] = $"key-id=KEY:RSA:p1.rec.net; data={signature}";
}
}
+8 -12
View File
@@ -1,18 +1,14 @@
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.Mvc;
namespace CannedNet.Services.Controllers;
public class NSController
//TODO: proper NameServer configs
[ApiController, Route("")]
public class NSController : ControllerBase
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
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");
});
[HttpGet]
public async Task<IResult> Get() {
string json = await System.IO.File.ReadAllTextAsync("JSON/endpoints.json");
return Results.Content(json, "application/json");
}
}
@@ -1,4 +1,4 @@
using CannedNet.Hubs;
/*using CannedNet.Hubs;
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.SignalR;
@@ -16,3 +16,4 @@ public class NotifyController
NotificationService.SetHubContext(hubContext);
}
}
*/
@@ -1,4 +1,3 @@
using System.Net.Mime;
using CannedNet.Services.Controllers;
namespace CannedNet.Services.Infrastructure;
@@ -16,15 +15,15 @@ public static class Services
{
public static readonly List<ServiceRegistry> All =
[
new() { Name = "NS", Port = 5001, ServiceType = typeof(NSController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NSController().MapEndpoints(app) },
//new() { Name = "NS", Port = 5001, ServiceType = typeof(NSController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NSController().MapEndpoints(app) },
new() { Name = "API", Port = 5000, ServiceType = typeof(APIController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new APIController().MapEndpoints(app) },
new() { Name = "Auth", Port = 5002, ServiceType = typeof(AuthController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AuthController().MapEndpoints(app) },
new() { Name = "Chat", Port = 5003, ServiceType = typeof(ChatController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ChatController().MapEndpoints(app) },
//new() { Name = "Auth", Port = 5002, ServiceType = typeof(AuthController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AuthController().MapEndpoints(app) },
//new() { Name = "Chat", Port = 5003, ServiceType = typeof(ChatController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ChatController().MapEndpoints(app) },
new() { Name = "Match", Port = 5004, ServiceType = typeof(MatchmakingController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new MatchmakingController().MapEndpoints(app) },
new() { Name = "Accounts", Port = 5005, ServiceType = typeof(AccountsController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AccountsController().MapEndpoints(app, jwt) },
new() { Name = "Notify", Port = 5006, ServiceType = typeof(NotifyController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NotifyController().MapEndpoints(app) },
//new() { Name = "Accounts", Port = 5005, ServiceType = typeof(AccountsController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AccountsController().MapEndpoints(app, jwt) },
//new() { Name = "Notify", Port = 5006, ServiceType = typeof(NotifyController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NotifyController().MapEndpoints(app) },
new() { Name = "CDN", Port = 5007, ServiceType = typeof(CDNController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new CDNController().MapEndpoints(app) },
new() { Name = "Image", Port = 5008, ServiceType = typeof(ImageController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ImageController().MapEndpoints(app) },
//new() { Name = "Image", Port = 5008, ServiceType = typeof(ImageController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ImageController().MapEndpoints(app) },
new() { Name = "Clubs", Port = 5009, ServiceType = typeof(ClubsController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ClubsController().MapEndpoints(app) },
new() { Name = "Commerce", Port = 5010, ServiceType = typeof(CommerceController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new CommerceController().MapEndpoints(app) },
new() { Name = "Econ", Port = 5011, ServiceType = typeof(EconController), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new EconController().MapEndpoints(app) },