This commit is contained in:
Lapis
2026-04-01 09:12:49 -04:00
parent da0f31ce5e
commit 3315a200d7
57 changed files with 2497 additions and 407 deletions
-38
View File
@@ -1,38 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace CannedNet.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Account> Accounts { get; set; }
public DbSet<CachedLogin> CachedLogins { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// accounts
modelBuilder.Entity<Account>(entity =>
{
entity.HasKey(e => e.AccountId);
entity.Property(e => e.AccountId).ValueGeneratedNever();
entity.Property(e => e.Username).IsRequired();
entity.Property(e => e.DisplayName).IsRequired();
entity.ToTable("accounts");
});
// cachedlogins
modelBuilder.Entity<CachedLogin>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.HasIndex(e => new { e.Platform, e.PlatformID }).IsUnique();
entity.ToTable("cached_logins");
});
}
}
-337
View File
@@ -1,337 +0,0 @@
using CannedNet;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// TODO: organize this into multiple files
// TODO: cdn stuff soon so i can not just have 0 images for this lmao
// TODO: setup signalr and get websockets running, currently should focus on notifications as logging in is being blocked by that
// needed so it won't try to auto capitalize random stuff that may be case-sensitive
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = null;
});
// postgresql db, will probably have it configurable later on but for now just hardcode it in
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres";
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));
builder.Services.AddScoped<JwtTokenService>();
var app = builder.Build();
// apply db migrations on startup so we don't have to worry about it later bc i hate doing db things manually lol
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate();
}
var jwtService = app.Services.GetRequiredService<JwtTokenService>();
// request logging
app.Use(async (context, next) =>
{
var request = context.Request;
Console.WriteLine($"\n{request.Method} {request.Path}{request.QueryString}");
foreach (var header in request.Headers)
Console.WriteLine($" {header.Key}: {string.Join(", ", header.Value)}");
if (request.ContentLength > 0)
{
request.EnableBuffering();
using (var reader = new StreamReader(request.Body, leaveOpen: true))
{
var body = await reader.ReadToEndAsync();
Console.WriteLine($" Body: {body}");
}
request.Body.Position = 0;
}
await next(context);
});
app.MapGet("/", () =>
{
var json = File.ReadAllText("endpoints.json");
return Results.Content(json, "application/json");
});
app.MapGet("/api/config/v1/amplitude", () =>
{
var config = new
{
AmplitudeKey = "fd846cd67066d19eec62fbdd00be9258",
StatSigKey = "client-mnew4Ggjuu3Fbh86Ed3tRgWUxapp0D7PPB0i3MXUEao",
RudderStackKey = "37qVlzZyXqdmZckXJ61vQeEJp8Y",
UseRudderStack = true
};
return Results.Ok(config);
});
app.MapGet("/api/config/v2", () =>
{
var json = File.ReadAllText("configv2.json");
return Results.Content(json, "application/json");
});
app.MapGet("/api/versioncheck/v4", () =>
{
return Results.Content("{\"VersionStatus\":0}","application/json");
});
app.MapGet("/api/gameconfigs/v1/all", () =>
{
var json = File.ReadAllText("gameconfigs.json");
return Results.Content(json, "application/json");
});
app.MapGet("/photon", () =>
{
var json = File.ReadAllText("photonsettings.json");
return Results.Content(json, "application/json");
});
app.MapGet("/eac/challenge", () =>
{
var file = File.ReadAllText("eacchallenge.txt");
return Results.Content(file,"text/plain");
});
app.MapGet("/api/playerReputation/v1/{id}", (string id) =>
{
// TODO: make rep actually work
return Results.Content($"{{\"AccountId\":{id},\"Noteriety\":0,\"CheerGeneral\":0,\"CheerHelpful\":0,\"CheerCreative\":0,\"CheerGreatHost\":0,\"CheerSportsman\":0,\"CheerCredit\":20,\"SelectedCheer\":null}}","application/json");
});
app.MapGet("/api/players/v1/progression/{id}", (string id) =>
{
// TODO: make progression actually work
return Results.Content($"{{\"PlayerId\":{id},\"Level\":1,\"XP\":0}}","application/json");
});
app.MapGet("/api/PlayerReporting/v1/moderationBlockDetails", () =>
{
// TODO: make this actually work
return Results.Content($"{{\"ReportCategory\":0,\"Duration\":0,\"GameSessionId\":0,\"IsHostKick\":false,\"Message\":\"\",\"PlayerIdReporter\":null,\"IsBan\":false}}","application/json");
});
app.MapPost("/player/login", () =>
{
return Results.Ok();
});
app.MapGet("/cachedlogin/forplatformid/{platform}/{id}", async (string platform, string id, AppDbContext db) =>
{
var platformType = int.Parse(platform);
var logins = await db.CachedLogins
.Where(c => c.Platform == (PlatformType)platformType && c.PlatformID == id)
.ToListAsync();
return Results.Json(logins.Any() ? logins : new List<object>());
});
app.MapGet("/account/me", (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) || !int.TryParse(accountId, out var id))
return Results.Unauthorized();
var selfAccount = new SelfAccount
{
AccountId = id,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{id}",
DisplayName = $"Player{id}",
CreatedAt = DateTime.UtcNow,
Email = null,
Phone = null,
JuniorState = null,
Birthday = null,
ParentAccountId = null,
AvailableUsernameChanges = 1
};
return Results.Ok(selfAccount);
});
app.MapGet("/account/bulk", (HttpRequest request) =>
{
var ids = request.Query["id"];
var accounts = new List<Account>();
foreach (var id in ids)
{
if (int.TryParse(id, out var accountId))
{
var account = new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
};
accounts.Add(account);
}
}
return Results.Json(accounts);
});
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))
{
if (contentType.Contains("application/x-www-form-urlencoded"))
{
var pairs = body.Split('&');
foreach (var pair in pairs)
{
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 = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.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))
{
var loginRecord = new CachedLogin
{
AccountId = accountId,
Platform = (PlatformType)platform,
PlatformID = platformId,
LastLoginTime = DateTime.UtcNow,
RequirePassword = false
};
db.CachedLogins.Add(loginRecord);
}
await db.SaveChangesAsync();
return Results.Ok(RecNetResultWithValue<Account>.Ok(account));
});
app.MapPost("/connect/token", async (HttpRequest httpRequest) =>
{
string accountId = "";
string platformId = "";
string platform = "";
if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0)
{
try
{
httpRequest.EnableBuffering();
using (var reader = new StreamReader(httpRequest.Body, leaveOpen: true))
{
var body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body))
{
var pairs = body.Split('&');
foreach (var pair in pairs)
{
var keyValue = pair.Split('=');
if (keyValue.Length == 2)
{
var key = Uri.UnescapeDataString(keyValue[0]);
var value = Uri.UnescapeDataString(keyValue[1]);
if (key == "account_id")
accountId = value;
else if (key == "platform_id")
platformId = value;
}
}
}
}
httpRequest.Body.Position = 0;
}
catch { }
}
var accessToken = jwtService.GenerateToken(accountId, platformId, platform);
var response = new
{
access_token = accessToken,
expires_in = 3600,
token_type = "Bearer",
refresh_token = Guid.NewGuid().ToString("N").ToUpper() + "-1",
scope = "offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage",
key = "8oQ+e+WQaOBPbEcakhqs3dwZZdOmmyDUmJSD9u4AHMY="
};
return Results.Json(response);
});
app.Run();
-26
View File
@@ -1,26 +0,0 @@
{
"Auth": "https://rr.lapis.codes",
"API": "https://rr.lapis.codes",
"WWW": "https://www.lapis.codes",
"Notifications": "https://rr.lapis.codes",
"Images": "https://rr.lapis.codes",
"CDN": "https://rr.lapis.codes",
"Commerce": "https://rr.lapis.codes",
"Matchmaking": "https://rr.lapis.codes",
"Storage": "https://rr.lapis.codes",
"Chat": "https://rr.lapis.codes",
"Leaderboard": "https://rr.lapis.codes",
"Accounts": "https://rr.lapis.codes",
"Link": "https://rr.lapis.codes",
"RoomComments": "https://rr.lapis.codes",
"Clubs": "https://rr.lapis.codes",
"Rooms": "https://rr.lapis.codes",
"PlatformNotifications": "https://rr.lapis.codes",
"Moderation": "https://rr.lapis.codes",
"DataCollection": "https://rr.lapis.codes",
"BugReporting": "https://rr.lapis.codes",
"Discovery": "https://rr.lapis.codes",
"PlayerSettings": "https://rr.lapis.codes",
"Rendering": "https://rr.lapis.codes",
"Launcher": "https://rr.lapis.codes"
}
+1 -1
View File
@@ -1,4 +1,4 @@
<Solution> <Solution>
<Folder Name="/Solution Items/" /> <Folder Name="/Solution Items/" />
<Project Path="CannedNet.API/CannedNet.API.csproj" /> <Project Path="CannedNet/CannedNet.csproj" />
</Solution> </Solution>
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5000"
}
}
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5005"
}
}
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5002"
}
}
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5003"
}
}
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5004"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5001"
}
}
@@ -0,0 +1,6 @@
{
"Hosting": {
"Url": "http://*:5006"
}
}
+85
View File
@@ -0,0 +1,85 @@
using Microsoft.EntityFrameworkCore;
namespace CannedNet.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Account> Accounts { get; set; }
public DbSet<CachedLogin> CachedLogins { get; set; }
public DbSet<PlayerProgression> PlayerProgressions { get; set; }
public DbSet<PlayerSetting> PlayerSettings { get; set; }
public DbSet<AvatarItem> AvatarItems { get; set; }
public DbSet<PlayerAvatar> PlayerAvatars { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// accounts
modelBuilder.Entity<Account>(entity =>
{
entity.HasKey(e => e.AccountId);
entity.Property(e => e.AccountId).ValueGeneratedNever();
entity.Property(e => e.Username).IsRequired();
entity.Property(e => e.DisplayName).IsRequired();
entity.ToTable("accounts");
});
// cachedlogins
modelBuilder.Entity<CachedLogin>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.HasIndex(e => new { e.Platform, e.PlatformID }).IsUnique();
entity.ToTable("cached_logins");
});
// player_progressions
modelBuilder.Entity<PlayerProgression>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.HasIndex(e => e.PlayerId).IsUnique();
entity.ToTable("player_progressions");
});
// player_settings_kv (key-value pairs)
modelBuilder.Entity<PlayerSetting>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.Property(e => e.PlayerId).IsRequired();
entity.Property(e => e.Key).IsRequired();
entity.Property(e => e.Value).IsRequired();
entity.HasIndex(e => new { e.PlayerId, e.Key }).IsUnique();
entity.ToTable("player_settings");
});
// avatar_items
modelBuilder.Entity<AvatarItem>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.Property(e => e.OwnerAccountId).IsRequired();
entity.Property(e => e.AvatarItemDesc).IsRequired();
entity.Property(e => e.FriendlyName).IsRequired();
entity.HasIndex(e => e.OwnerAccountId);
entity.ToTable("avatar_items");
});
// player_avatars
modelBuilder.Entity<PlayerAvatar>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).ValueGeneratedOnAdd();
entity.Property(e => e.OwnerAccountId).IsRequired();
entity.HasIndex(e => e.OwnerAccountId).IsUnique();
entity.ToTable("player_avatars");
});
}
}
+42
View File
@@ -0,0 +1,42 @@
using System.Text.Json;
using CannedNet.Services;
using Microsoft.AspNetCore.SignalR;
namespace CannedNet.Hubs;
public class NotificationsHub : Hub
{
private static NotificationService _notificationService = new();
public NotificationsHub()
{
}
public override async Task OnConnectedAsync()
{
_notificationService.OnConnected(Context.ConnectionId);
await Clients.Caller.SendAsync("OnConnect");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_notificationService.OnDisconnected(Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
public async Task SubscribeToPlayers(SubscriptionList subscriptionList)
{
await _notificationService.SubscribeToPlayers(Context.ConnectionId, subscriptionList);
}
public Task<HashSet<int>> GetSubscriptions()
{
return Task.FromResult(_notificationService.GetSubscribedPlayers(Context.ConnectionId).ToHashSet());
}
public static NotificationService GetNotificationService()
{
return _notificationService;
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"Auth": "https://auth.lapis.codes",
"API": "https://api.lapis.codes",
"WWW": "https://www.lapis.codes",
"Notifications": "https://notify.lapis.codes",
"Images": "https://img.lapis.codes",
"CDN": "https://cdn.lapis.codes",
"Commerce": "https://commerce.lapis.codes",
"Matchmaking": "https://match.lapis.codes",
"Storage": "https://storage.lapis.codes",
"Chat": "https://chatrr.lapis.codes",
"Leaderboard": "https://rr.lapis.codes",
"Accounts": "http://localhost:5005",
"Link": "https://link.lapis.codes",
"RoomComments": "https://roomcomments.lapis.codes",
"Clubs": "https://clubs.lapis.codes",
"Rooms": "https://rooms.lapis.codes",
"PlatformNotifications": "https://api.lapis.codes",
"Moderation": "https://api.lapis.codes",
"DataCollection": "https://api.lapis.codes",
"BugReporting": "https://api.lapis.codes",
"Discovery": "https://api.lapis.codes",
"PlayerSettings": "https://api.lapis.codes",
"Rendering": "https://api.lapis.codes",
"Launcher": "https://rr.lapis.codes"
}
@@ -0,0 +1,112 @@
// <auto-generated />
using System;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260331191343_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CannedNet.Account", b =>
{
b.Property<int?>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "createdAt");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "displayName");
b.Property<int?>("IdentityFlags")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "identityFlags");
b.Property<bool>("IsJunior")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "isJunior");
b.Property<int?>("PersonalPronouns")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
b.Property<int?>("Platforms")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platforms");
b.Property<string>("ProfileImage")
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "profileImage");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "username");
b.HasKey("AccountId");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("CannedNet.CachedLogin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("LastLoginTime")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
b.Property<int>("Platform")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platform");
b.Property<string>("PlatformID")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "platformId");
b.Property<bool>("RequirePassword")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "requirePassword");
b.HasKey("Id");
b.HasIndex("Platform", "PlatformID")
.IsUnique();
b.ToTable("cached_logins", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,68 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "accounts",
columns: table => new
{
AccountId = table.Column<int>(type: "integer", nullable: false),
ProfileImage = table.Column<string>(type: "text", nullable: true),
IsJunior = table.Column<bool>(type: "boolean", nullable: false),
Platforms = table.Column<int>(type: "integer", nullable: true),
PersonalPronouns = table.Column<int>(type: "integer", nullable: true),
IdentityFlags = table.Column<int>(type: "integer", nullable: true),
Username = table.Column<string>(type: "text", nullable: false),
DisplayName = table.Column<string>(type: "text", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_accounts", x => x.AccountId);
});
migrationBuilder.CreateTable(
name: "cached_logins",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
AccountId = table.Column<int>(type: "integer", nullable: false),
Platform = table.Column<int>(type: "integer", nullable: false),
PlatformID = table.Column<string>(type: "text", nullable: false),
LastLoginTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
RequirePassword = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_cached_logins", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_cached_logins_Platform_PlatformID",
table: "cached_logins",
columns: new[] { "Platform", "PlatformID" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "accounts");
migrationBuilder.DropTable(
name: "cached_logins");
}
}
}
@@ -0,0 +1,140 @@
// <auto-generated />
using System;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260401002616_AddPlayerProgressions")]
partial class AddPlayerProgressions
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CannedNet.Account", b =>
{
b.Property<int?>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "createdAt");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "displayName");
b.Property<int?>("IdentityFlags")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "identityFlags");
b.Property<bool>("IsJunior")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "isJunior");
b.Property<int?>("PersonalPronouns")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
b.Property<int?>("Platforms")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platforms");
b.Property<string>("ProfileImage")
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "profileImage");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "username");
b.HasKey("AccountId");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("CannedNet.CachedLogin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("LastLoginTime")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
b.Property<int>("Platform")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platform");
b.Property<string>("PlatformID")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "platformId");
b.Property<bool>("RequirePassword")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "requirePassword");
b.HasKey("Id");
b.HasIndex("Platform", "PlatformID")
.IsUnique();
b.ToTable("cached_logins", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerProgression", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Level")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "level");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<int>("Xp")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "xp");
b.HasKey("Id");
b.HasIndex("PlayerId")
.IsUnique();
b.ToTable("player_progressions", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
/// <inheritdoc />
public partial class AddPlayerProgressions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "player_progressions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
PlayerId = table.Column<int>(type: "integer", nullable: false),
Level = table.Column<int>(type: "integer", nullable: false),
Xp = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_player_progressions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_player_progressions_PlayerId",
table: "player_progressions",
column: "PlayerId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "player_progressions");
}
}
}
@@ -0,0 +1,170 @@
// <auto-generated />
using System;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260401123437_AddPlayerSettings")]
partial class AddPlayerSettings
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CannedNet.Account", b =>
{
b.Property<int?>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "createdAt");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "displayName");
b.Property<int?>("IdentityFlags")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "identityFlags");
b.Property<bool>("IsJunior")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "isJunior");
b.Property<int?>("PersonalPronouns")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
b.Property<int?>("Platforms")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platforms");
b.Property<string>("ProfileImage")
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "profileImage");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "username");
b.HasKey("AccountId");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("CannedNet.CachedLogin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("LastLoginTime")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
b.Property<int>("Platform")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platform");
b.Property<string>("PlatformID")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "platformId");
b.Property<bool>("RequirePassword")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "requirePassword");
b.HasKey("Id");
b.HasIndex("Platform", "PlatformID")
.IsUnique();
b.ToTable("cached_logins", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerProgression", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Level")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "level");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<int>("Xp")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "xp");
b.HasKey("Id");
b.HasIndex("PlayerId")
.IsUnique();
b.ToTable("player_progressions", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "key");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "value");
b.HasKey("Id");
b.HasIndex("PlayerId", "Key")
.IsUnique();
b.ToTable("player_settings", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
/// <inheritdoc />
public partial class AddPlayerSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "player_settings",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
PlayerId = table.Column<int>(type: "integer", nullable: false),
Key = table.Column<string>(type: "text", nullable: false),
Value = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_player_settings", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_player_settings_PlayerId_Key",
table: "player_settings",
columns: new[] { "PlayerId", "Key" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "player_settings");
}
}
}
@@ -0,0 +1,254 @@
// <auto-generated />
using System;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260401125651_AddPlayerAvatars")]
partial class AddPlayerAvatars
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CannedNet.Account", b =>
{
b.Property<int?>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "createdAt");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "displayName");
b.Property<int?>("IdentityFlags")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "identityFlags");
b.Property<bool>("IsJunior")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "isJunior");
b.Property<int?>("PersonalPronouns")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
b.Property<int?>("Platforms")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platforms");
b.Property<string>("ProfileImage")
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "profileImage");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "username");
b.HasKey("AccountId");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("CannedNet.AvatarItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarItemDesc")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "avatarItemDesc");
b.Property<int>("AvatarItemType")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "avatarItemType");
b.Property<string>("FriendlyName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "friendlyName");
b.Property<int>("OwnerAccountId")
.HasColumnType("integer");
b.Property<int>("PlatformMask")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platformMask");
b.Property<int>("Rarity")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "rarity");
b.Property<string>("Tooltip")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "tooltip");
b.HasKey("Id");
b.HasIndex("OwnerAccountId");
b.ToTable("avatar_items", (string)null);
});
modelBuilder.Entity("CannedNet.CachedLogin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("LastLoginTime")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
b.Property<int>("Platform")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platform");
b.Property<string>("PlatformID")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "platformId");
b.Property<bool>("RequirePassword")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "requirePassword");
b.HasKey("Id");
b.HasIndex("Platform", "PlatformID")
.IsUnique();
b.ToTable("cached_logins", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerAvatar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("FaceFeatures")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "FaceFeatures");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "HairColor");
b.Property<string>("OutfitSelections")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "OutfitSelections");
b.Property<int>("OwnerAccountId")
.HasColumnType("integer");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "SkinColor");
b.HasKey("Id");
b.HasIndex("OwnerAccountId")
.IsUnique();
b.ToTable("player_avatars", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerProgression", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Level")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "level");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<int>("Xp")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "xp");
b.HasKey("Id");
b.HasIndex("PlayerId")
.IsUnique();
b.ToTable("player_progressions", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "key");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "value");
b.HasKey("Id");
b.HasIndex("PlayerId", "Key")
.IsUnique();
b.ToTable("player_settings", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
/// <inheritdoc />
public partial class AddPlayerAvatars : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "avatar_items",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
AvatarItemType = table.Column<int>(type: "integer", nullable: false),
AvatarItemDesc = table.Column<string>(type: "text", nullable: false),
PlatformMask = table.Column<int>(type: "integer", nullable: false),
FriendlyName = table.Column<string>(type: "text", nullable: false),
Tooltip = table.Column<string>(type: "text", nullable: false),
Rarity = table.Column<int>(type: "integer", nullable: false),
OwnerAccountId = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_avatar_items", x => x.Id);
});
migrationBuilder.CreateTable(
name: "player_avatars",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OwnerAccountId = table.Column<int>(type: "integer", nullable: false),
OutfitSelections = table.Column<string>(type: "text", nullable: false),
FaceFeatures = table.Column<string>(type: "text", nullable: false),
SkinColor = table.Column<string>(type: "text", nullable: false),
HairColor = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_player_avatars", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_avatar_items_OwnerAccountId",
table: "avatar_items",
column: "OwnerAccountId");
migrationBuilder.CreateIndex(
name: "IX_player_avatars_OwnerAccountId",
table: "player_avatars",
column: "OwnerAccountId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "avatar_items");
migrationBuilder.DropTable(
name: "player_avatars");
}
}
}
@@ -0,0 +1,251 @@
// <auto-generated />
using System;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace CannedNet.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CannedNet.Account", b =>
{
b.Property<int?>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "createdAt");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "displayName");
b.Property<int?>("IdentityFlags")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "identityFlags");
b.Property<bool>("IsJunior")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "isJunior");
b.Property<int?>("PersonalPronouns")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "personalPronouns");
b.Property<int?>("Platforms")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platforms");
b.Property<string>("ProfileImage")
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "profileImage");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "username");
b.HasKey("AccountId");
b.ToTable("accounts", (string)null);
});
modelBuilder.Entity("CannedNet.AvatarItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AvatarItemDesc")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "avatarItemDesc");
b.Property<int>("AvatarItemType")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "avatarItemType");
b.Property<string>("FriendlyName")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "friendlyName");
b.Property<int>("OwnerAccountId")
.HasColumnType("integer");
b.Property<int>("PlatformMask")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platformMask");
b.Property<int>("Rarity")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "rarity");
b.Property<string>("Tooltip")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "tooltip");
b.HasKey("Id");
b.HasIndex("OwnerAccountId");
b.ToTable("avatar_items", (string)null);
});
modelBuilder.Entity("CannedNet.CachedLogin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "accountId");
b.Property<DateTime>("LastLoginTime")
.HasColumnType("timestamp with time zone")
.HasAnnotation("Relational:JsonPropertyName", "lastLoginTime");
b.Property<int>("Platform")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "platform");
b.Property<string>("PlatformID")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "platformId");
b.Property<bool>("RequirePassword")
.HasColumnType("boolean")
.HasAnnotation("Relational:JsonPropertyName", "requirePassword");
b.HasKey("Id");
b.HasIndex("Platform", "PlatformID")
.IsUnique();
b.ToTable("cached_logins", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerAvatar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("FaceFeatures")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "FaceFeatures");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "HairColor");
b.Property<string>("OutfitSelections")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "OutfitSelections");
b.Property<int>("OwnerAccountId")
.HasColumnType("integer");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "SkinColor");
b.HasKey("Id");
b.HasIndex("OwnerAccountId")
.IsUnique();
b.ToTable("player_avatars", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerProgression", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Level")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "level");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<int>("Xp")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "xp");
b.HasKey("Id");
b.HasIndex("PlayerId")
.IsUnique();
b.ToTable("player_progressions", (string)null);
});
modelBuilder.Entity("CannedNet.PlayerSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "key");
b.Property<int>("PlayerId")
.HasColumnType("integer")
.HasAnnotation("Relational:JsonPropertyName", "playerId");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text")
.HasAnnotation("Relational:JsonPropertyName", "value");
b.HasKey("Id");
b.HasIndex("PlayerId", "Key")
.IsUnique();
b.ToTable("player_settings", (string)null);
});
#pragma warning restore 612, 618
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class AvatarItem
{
public int Id { get; set; }
[JsonPropertyName("avatarItemType")]
public int AvatarItemType { get; set; }
[JsonPropertyName("avatarItemDesc")]
public string AvatarItemDesc { get; set; } = "";
[JsonPropertyName("platformMask")]
public int PlatformMask { get; set; } = -1;
[JsonPropertyName("friendlyName")]
public string FriendlyName { get; set; } = "";
[JsonPropertyName("tooltip")]
public string Tooltip { get; set; } = "";
[JsonPropertyName("rarity")]
public int Rarity { get; set; } = 0;
public int OwnerAccountId { get; set; }
}
+231
View File
@@ -0,0 +1,231 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class NotificationMessage
{
[JsonPropertyName("notificationId")]
public string? NotificationId { get; set; }
[JsonPropertyName("notificationType")]
public string? NotificationType { get; set; }
[JsonPropertyName("id")]
public int? Id { get; set; }
[JsonPropertyName("fromAccountId")]
public int? FromAccountId { get; set; }
[JsonPropertyName("toAccountId")]
public int? ToAccountId { get; set; }
[JsonPropertyName("createdAt")]
public DateTime? CreatedAt { get; set; }
[JsonPropertyName("data")]
public Dictionary<string, object>? Data { get; set; }
}
public class SubscriptionUpdateProfile
{
[JsonPropertyName("accountId")]
public int? AccountId { get; set; }
[JsonPropertyName("profileImage")]
public string? ProfileImage { get; set; }
[JsonPropertyName("displayName")]
public string? DisplayName { get; set; }
[JsonPropertyName("username")]
public string? Username { get; set; }
}
public class SubscriptionUpdatePresence
{
[JsonPropertyName("accountId")]
public int? AccountId { get; set; }
[JsonPropertyName("presence")]
public string? Presence { get; set; }
[JsonPropertyName("currentRoomId")]
public string? CurrentRoomId { get; set; }
[JsonPropertyName("currentWorldId")]
public string? CurrentWorldId { get; set; }
[JsonPropertyName("lastSeen")]
public DateTime? LastSeen { get; set; }
}
public class SubscriptionUpdateGameSession
{
[JsonPropertyName("gameSessionId")]
public string? GameSessionId { get; set; }
[JsonPropertyName("roomId")]
public string? RoomId { get; set; }
[JsonPropertyName("worldId")]
public string? WorldId { get; set; }
[JsonPropertyName("players")]
public List<int>? Players { get; set; }
}
public class SubscriptionUpdateRoom
{
[JsonPropertyName("roomId")]
public string? RoomId { get; set; }
[JsonPropertyName("worldId")]
public string? WorldId { get; set; }
[JsonPropertyName("roomName")]
public string? RoomName { get; set; }
[JsonPropertyName("version")]
public string? Version { get; set; }
}
public class RelationshipChangedNotification
{
[JsonPropertyName("accountId")]
public int? AccountId { get; set; }
[JsonPropertyName("relationshipType")]
public string? RelationshipType { get; set; }
[JsonPropertyName("status")]
public string? Status { get; set; }
}
public class PresenceHeartbeatResponse
{
[JsonPropertyName("currentRoomId")]
public string? CurrentRoomId { get; set; }
[JsonPropertyName("currentWorldId")]
public string? CurrentWorldId { get; set; }
[JsonPropertyName("presence")]
public string? Presence { get; set; }
}
public class ModerationNotification
{
[JsonPropertyName("reason")]
public string? Reason { get; set; }
[JsonPropertyName("duration")]
public int? Duration { get; set; }
[JsonPropertyName("moderatedBy")]
public string? ModeratedBy { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
[JsonPropertyName("gameSessionId")]
public string? GameSessionId { get; set; }
}
public class GiftPackageNotification
{
[JsonPropertyName("giftPackageId")]
public string? GiftPackageId { get; set; }
[JsonPropertyName("fromAccountId")]
public int? FromAccountId { get; set; }
[JsonPropertyName("giftType")]
public string? GiftType { get; set; }
}
public class StorefrontBalanceNotification
{
[JsonPropertyName("balance")]
public decimal? Balance { get; set; }
[JsonPropertyName("currency")]
public string? Currency { get; set; }
[JsonPropertyName("purchaseId")]
public string? PurchaseId { get; set; }
}
public class ConsumableMappingNotification
{
[JsonPropertyName("mappingId")]
public string? MappingId { get; set; }
[JsonPropertyName("consumableId")]
public string? ConsumableId { get; set; }
[JsonPropertyName("quantity")]
public int? Quantity { get; set; }
}
public class PlayerEventNotification
{
[JsonPropertyName("eventId")]
public string? EventId { get; set; }
[JsonPropertyName("eventName")]
public string? EventName { get; set; }
[JsonPropertyName("eventType")]
public string? EventType { get; set; }
[JsonPropertyName("worldId")]
public string? WorldId { get; set; }
[JsonPropertyName("responseStatus")]
public string? ResponseStatus { get; set; }
[JsonPropertyName("state")]
public string? State { get; set; }
}
public class ChatMessageNotification
{
[JsonPropertyName("channelId")]
public string? ChannelId { get; set; }
[JsonPropertyName("fromAccountId")]
public int? FromAccountId { get; set; }
[JsonPropertyName("messageId")]
public string? MessageId { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
[JsonPropertyName("channelType")]
public string? ChannelType { get; set; }
}
public class CommunityBoardNotification
{
[JsonPropertyName("boardId")]
public string? BoardId { get; set; }
[JsonPropertyName("announcementId")]
public string? AnnouncementId { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
[JsonPropertyName("content")]
public string? Content { get; set; }
}
public class InventionModerationNotification
{
[JsonPropertyName("inventionId")]
public string? InventionId { get; set; }
[JsonPropertyName("moderationState")]
public string? ModerationState { get; set; }
}
+40
View File
@@ -0,0 +1,40 @@
namespace CannedNet;
public enum PushNotificationId
{
RelationshipChanged = 1,
MessageReceived = 2,
MessageDeleted = 3,
PresenceHeartbeatResponse = 4,
RefreshLogin = 5,
Logout = 6,
SubscriptionUpdateProfile = 11,
SubscriptionUpdatePresence = 12,
SubscriptionUpdateGameSession = 13,
SubscriptionUpdateRoom = 15,
ModerationQuitGame = 20,
ModerationUpdateRequired = 21,
ModerationKick = 22,
ModerationKickAttemptFailed = 23,
ModerationRoomBan = 24,
ServerMaintenance = 25,
GiftPackageReceived = 30,
GiftPackageReceivedImmediate = 31,
ProfileJuniorStatusUpdate = 40,
RelationshipsInvalid = 50,
StorefrontBalanceAdd = 60,
StorefrontBalanceUpdate = 61,
StorefrontBalancePurchase = 62,
ConsumableMappingAdded = 70,
ConsumableMappingRemoved = 71,
PlayerEventCreated = 80,
PlayerEventUpdated = 81,
PlayerEventDeleted = 82,
PlayerEventResponseChanged = 83,
PlayerEventResponseDeleted = 84,
PlayerEventStateChanged = 85,
ChatMessageReceived = 90,
CommunityBoardUpdate = 95,
CommunityBoardAnnouncementUpdate = 96,
InventionModerationStateChanged = 100
}
+21
View File
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class PlayerAvatar
{
public int Id { get; set; }
public int OwnerAccountId { get; set; }
[JsonPropertyName("OutfitSelections")]
public string OutfitSelections { get; set; } = "";
[JsonPropertyName("FaceFeatures")]
public string FaceFeatures { get; set; } = "{}";
[JsonPropertyName("SkinColor")]
public string SkinColor { get; set; } = "";
[JsonPropertyName("HairColor")]
public string HairColor { get; set; } = "";
}
+24
View File
@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class PlayerProgression
{
public int Id { get; set; }
[JsonPropertyName("playerId")]
public int PlayerId { get; set; }
[JsonPropertyName("level")]
public int Level { get; set; }
[JsonPropertyName("xp")]
public int Xp { get; set; }
}
public class PlayerProgressionBulkResponse
{
[JsonPropertyName("playerId")]
public int PlayerId { get; set; }
[JsonPropertyName("level")]
public int Level { get; set; }
[JsonPropertyName("xp")]
public int Xp { get; set; }
}
+15
View File
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class PlayerSetting
{
public int Id { get; set; }
[JsonPropertyName("playerId")]
public int PlayerId { get; set; }
[JsonPropertyName("key")]
public string Key { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace CannedNet;
public class SettingPayload
{
public string Key { get; set; }
public string Value { get; set; }
}
+9
View File
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace CannedNet;
public class SubscriptionList
{
[JsonPropertyName("playerIds")]
public List<int>? PlayerIds { get; set; }
}
+25
View File
@@ -0,0 +1,25 @@
using CannedNet;
using CannedNet.Services;
using Microsoft.EntityFrameworkCore;
var apps = new List<(WebApplication App, ServiceRegistry Service)>();
foreach (var service in Services.All)
{
var builder = WebApplication.CreateBuilder();
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();
scope.ServiceProvider.GetRequiredService<CannedNet.Data.AppDbContext>().Database.Migrate();
var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>();
foreach (var (app, service) in apps)
service.MapEndpoints(app, jwtService);
await Task.WhenAll(apps.Select(t => t.App.RunAsync()));
+249
View File
@@ -0,0 +1,249 @@
using CannedNet;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
namespace CannedNet.Services;
public class APIService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
var jwtService = app.Services.GetRequiredService<JwtTokenService>();
app.MapGet("/api/config/v1/amplitude", () => Results.Ok(new
{
AmplitudeKey = "fd846cd67066d19eec62fbdd00be9258",
StatSigKey = "client-mnew4Ggjuu3Fbh86Ed3tRgWUxapp0D7PPB0i3MXUEao",
RudderStackKey = "37qVlzZyXqdmZckXJ61vQeEJp8Y",
UseRudderStack = false
}));
app.MapGet("/api/config/v2", () => Results.Content(File.ReadAllText("JSON/configv2.json"), "application/json"));
app.MapGet("/api/versioncheck/v4", () => Results.Content("{\"VersionStatus\":0}", "application/json"));
app.MapGet("/api/gameconfigs/v1/all", () => Results.Content(File.ReadAllText("JSON/gameconfigs.json"), "application/json"));
app.MapGet("/api/relationships/v2/get", () => Results.Content("[]", "application/json"));
app.MapGet("/api/messages/v2/get", () => Results.Content("[]", "application/json"));
app.MapGet("/api/playerReputation/v1/{id}", (string id) =>
Results.Content($"{{\"AccountId\":{id},\"Noteriety\":0,\"CheerGeneral\":0,\"CheerHelpful\":0,\"CheerCreative\":0,\"CheerGreatHost\":0,\"CheerSportsman\":0,\"CheerCredit\":20,\"SelectedCheer\":null}}", "application/json"));
app.MapGet("/api/players/v1/progression/{id}", (string id) =>
Results.Content($"{{\"PlayerId\":{id},\"Level\":1,\"XP\":0}}", "application/json"));
app.MapPost("/api/players/v1/progression/bulk", async (HttpRequest httpRequest, AppDbContext db) =>
{
var ids = new List<int>();
if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0)
{
httpRequest.EnableBuffering();
using var reader = new StreamReader(httpRequest.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 && keyValue[0] == "Ids")
{
var idString = Uri.UnescapeDataString(keyValue[1]);
foreach (var id in idString.Split(','))
if (int.TryParse(id, out var parsedId))
ids.Add(parsedId);
break;
}
}
}
httpRequest.Body.Position = 0;
}
if (!ids.Any())
return Results.Json(new List<PlayerProgressionBulkResponse>());
var progressions = await db.PlayerProgressions
.Where(p => ids.Contains(p.PlayerId))
.Select(p => new PlayerProgressionBulkResponse { PlayerId = p.PlayerId, Level = p.Level, Xp = p.Xp })
.ToListAsync();
return Results.Json(progressions);
});
app.MapGet("/api/avatar/v4/items", 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();
var items = await db.AvatarItems
.Where(i => i.OwnerAccountId == id)
.ToListAsync();
return Results.Json(items);
});
app.MapGet("/api/avatar/v2", 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();
var avatar = await db.PlayerAvatars
.FirstOrDefaultAsync(a => a.OwnerAccountId == id);
if (avatar == null)
{
avatar = new PlayerAvatar
{
OwnerAccountId = id,
OutfitSelections = "",
FaceFeatures = "{}",
SkinColor = "",
HairColor = ""
};
db.PlayerAvatars.Add(avatar);
await db.SaveChangesAsync();
}
return Results.Json(avatar);
});
app.MapPost("/api/avatar/v2/set", 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();
request.EnableBuffering();
var avatarUpdate = await System.Text.Json.JsonSerializer.DeserializeAsync<PlayerAvatar>(request.Body);
if (avatarUpdate == null)
return Results.BadRequest();
var avatar = await db.PlayerAvatars
.FirstOrDefaultAsync(a => a.OwnerAccountId == id);
if (avatar == null)
{
avatar = new PlayerAvatar { OwnerAccountId = id };
db.PlayerAvatars.Add(avatar);
}
avatar.OutfitSelections = avatarUpdate.OutfitSelections;
avatar.FaceFeatures = avatarUpdate.FaceFeatures;
avatar.SkinColor = avatarUpdate.SkinColor;
avatar.HairColor = avatarUpdate.HairColor;
await db.SaveChangesAsync();
return Results.Ok(avatar);
});
app.MapGet("/api/PlayerReporting/v1/moderationBlockDetails", () =>
Results.Content("{\"ReportCategory\":0,\"Duration\":0,\"GameSessionId\":0,\"IsHostKick\":false,\"Message\":\"\",\"PlayerIdReporter\":null,\"IsBan\":false}", "application/json"));
app.MapGet("/api/settings/v2" +
"", 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();
var settings = await db.PlayerSettings
.Where(s => s.PlayerId == id)
.ToListAsync();
return Results.Json(settings);
});
app.MapPost("/api/settings/v2/set", 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();
request.EnableBuffering();
request.Body.Position = 0;
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var settings = new List<PlayerSetting>();
if (body.TrimStart().StartsWith("["))
{
settings = System.Text.Json.JsonSerializer.Deserialize<List<PlayerSetting>>(body) ?? [];
}
else
{
var single = System.Text.Json.JsonSerializer.Deserialize<PlayerSetting>(body);
if (single != null) settings.Add(single);
}
if (!settings.Any())
return Results.BadRequest("No settings provided");
db.PlayerSettings.RemoveRange(db.PlayerSettings.Where(s => s.PlayerId == id));
foreach (var setting in settings)
{
setting.PlayerId = id;
db.PlayerSettings.Add(setting);
}
await db.SaveChangesAsync();
return Results.Ok();
});
}
}
+151
View File
@@ -0,0 +1,151 @@
using CannedNet.Data;
namespace CannedNet.Services;
public class AccountsService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app, JwtTokenService jwtService)
{
app.MapGet("/", (HttpRequest request) =>
{
return Results.Ok("FUCKL MYSJDIOJD");
});
app.MapGet("/account/me", (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();
var selfAccount = new SelfAccount
{
AccountId = id,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{id}",
DisplayName = $"Player{id}",
CreatedAt = DateTime.UtcNow,
Email = null,
Phone = null,
JuniorState = null,
Birthday = null,
ParentAccountId = null,
AvailableUsernameChanges = 1
};
return Results.Ok(selfAccount);
});
app.MapGet("/account/bulk", (HttpRequest request) =>
{
var ids = request.Query["id"];
var accounts = new List<Account>();
foreach (var id in ids)
{
if (int.TryParse(id, out var accountId))
{
accounts.Add(new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
});
}
}
return Results.Json(accounts);
});
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 = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.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();
return Results.Ok(RecNetResultWithValue<Account>.Ok(account));
});
}
}
+80
View File
@@ -0,0 +1,80 @@
using CannedNet;
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
namespace CannedNet.Services;
public class AuthService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
var jwtService = app.Services.GetRequiredService<JwtTokenService>();
app.MapGet("/eac/challenge", () =>
{
var file = File.ReadAllText("JSON/eacchallenge.txt");
return Results.Content(file, "text/plain");
});
app.MapGet("/cachedlogin/forplatformid/{platform}/{id}", async (string platform, string id, AppDbContext db) =>
{
var platformType = int.Parse(platform);
var logins = await db.CachedLogins
.Where(c => c.Platform == (PlatformType)platformType && c.PlatformID == id)
.ToListAsync();
return Results.Json(logins.Any() ? logins : new List<object>());
});
app.MapPost("/connect/token", async (HttpRequest httpRequest) =>
{
string accountId = "";
string platformId = "";
string platform = "";
if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0)
{
try
{
httpRequest.EnableBuffering();
using var reader = new StreamReader(httpRequest.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 == "account_id")
accountId = value;
else if (key == "platform_id")
platformId = value;
}
}
}
httpRequest.Body.Position = 0;
}
catch { }
}
var accessToken = jwtService.GenerateToken(accountId, platformId, platform);
return Results.Json(new
{
access_token = accessToken,
expires_in = 3600,
token_type = "Bearer",
refresh_token = Guid.NewGuid().ToString("N").ToUpper() + "-1",
scope = "offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage",
key = "8oQ+e+WQaOBPbEcakhqs3dwZZdOmmyDUmJSD9u4AHMY="
});
});
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace CannedNet.Services;
public class ChatService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
app.MapGet("/thread", () => Results.Content("[]", "application/json"));
}
}
@@ -65,8 +65,8 @@ public class JwtTokenService
var handler = new JwtSecurityTokenHandler(); var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken( var token = handler.CreateJwtSecurityToken(
issuer: "https://auth.epicquest.live", issuer: "https://auth.lapis.codes",
audience: "https://auth.epicquest.live/resources", audience: "https://auth.lapis.codes/resources",
subject: identity, subject: identity,
notBefore: now, notBefore: now,
issuedAt: now, issuedAt: now,
@@ -88,9 +88,9 @@ public class JwtTokenService
ValidateIssuerSigningKey = true, ValidateIssuerSigningKey = true,
IssuerSigningKey = _securityKey, IssuerSigningKey = _securityKey,
ValidateIssuer = true, ValidateIssuer = true,
ValidIssuer = "https://auth.epicquest.live", ValidIssuer = "https://auth.lapis.codes",
ValidateAudience = true, ValidateAudience = true,
ValidAudience = "https://auth.epicquest.live/resources", ValidAudience = "https://auth.lapis.codes/resources",
ValidateLifetime = true, ValidateLifetime = true,
ClockSkew = TimeSpan.Zero ClockSkew = TimeSpan.Zero
}; };
+35
View File
@@ -0,0 +1,35 @@
namespace CannedNet.Services;
public class MatchmakingService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
app.MapPost("/player/login", () => Results.Ok());
app.MapGet("/player", (HttpRequest request) =>
{
var id = request.Query["id"];
var accounts = new List<Account>();
if (int.TryParse(id, out var accountId))
{
accounts.Add(new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
});
}
return Results.Json(accounts);
});
}
}
+23
View File
@@ -0,0 +1,23 @@
using CannedNet.Hubs;
namespace CannedNet.Services;
public class NSService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
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");
});
}
}
+117
View File
@@ -0,0 +1,117 @@
using System.Collections.Concurrent;
using Microsoft.AspNetCore.SignalR;
namespace CannedNet.Services;
public class NotificationService
{
private readonly ConcurrentDictionary<string, HashSet<int>> _connectionSubscriptions = new();
private readonly ConcurrentDictionary<int, HashSet<string>> _playerConnections = new();
public void OnConnected(string connectionId)
{
_connectionSubscriptions.TryAdd(connectionId, new HashSet<int>());
}
public void OnDisconnected(string connectionId)
{
if (_connectionSubscriptions.TryRemove(connectionId, out var subscriptions))
{
foreach (var playerId in subscriptions)
{
if (_playerConnections.TryGetValue(playerId, out var connections))
{
connections.Remove(connectionId);
if (connections.Count == 0)
{
_playerConnections.TryRemove(playerId, out _);
}
}
}
}
}
public Task SubscribeToPlayers(string connectionId, SubscriptionList subscriptionList)
{
var oldSubscriptions = _connectionSubscriptions.GetValueOrDefault(connectionId, new HashSet<int>());
var newPlayerIds = subscriptionList.PlayerIds ?? new List<int>();
_connectionSubscriptions[connectionId] = new HashSet<int>(newPlayerIds);
foreach (var playerId in newPlayerIds)
{
_playerConnections.AddOrUpdate(
playerId,
_ => new HashSet<string> { connectionId },
(_, existing) =>
{
existing.Add(connectionId);
return existing;
});
}
foreach (var playerId in oldSubscriptions)
{
if (!newPlayerIds.Contains(playerId) && _playerConnections.TryGetValue(playerId, out var connections))
{
connections.Remove(connectionId);
}
}
return Task.CompletedTask;
}
public HashSet<int>? GetSubscriptions(string connectionId)
{
return _connectionSubscriptions.GetValueOrDefault(connectionId);
}
public IEnumerable<string> GetConnectionsForPlayer(int playerId)
{
if (_playerConnections.TryGetValue(playerId, out var connections))
{
return connections.ToList();
}
return Enumerable.Empty<string>();
}
public IEnumerable<int> GetSubscribedPlayers(string connectionId)
{
if (_connectionSubscriptions.TryGetValue(connectionId, out var subscriptions))
{
return subscriptions.ToList();
}
return Enumerable.Empty<int>();
}
public async Task SendNotificationToPlayer(string callerConnectionId, IHubContext hubContext, int playerId, NotificationMessage notification)
{
var connections = GetConnectionsForPlayer(playerId);
foreach (var connectionId in connections)
{
if (connectionId == callerConnectionId)
continue;
await hubContext.Clients.Client(connectionId).SendAsync("Notification", notification);
}
}
public async Task BroadcastNotification(IHubContext hubContext, NotificationMessage notification)
{
await hubContext.Clients.All.SendAsync("Notification", notification);
}
public NotificationMessage CreateNotification(string notificationType, int? id = null, int? fromAccountId = null, int? toAccountId = null, Dictionary<string, object>? data = null)
{
return new NotificationMessage
{
NotificationId = Guid.NewGuid().ToString("N"),
NotificationType = notificationType,
Id = id,
FromAccountId = fromAccountId,
ToAccountId = toAccountId,
CreatedAt = DateTime.UtcNow,
Data = data
};
}
}
+13
View File
@@ -0,0 +1,13 @@
using CannedNet.Hubs;
namespace CannedNet.Services;
public class NotifyService
{
public WebApplicationBuilder Initialize(string[]? args = null) => ServiceExtensions.CreateRecNetBuilder(args);
public void MapEndpoints(WebApplication app)
{
app.MapHub<NotificationsHub>("/hub/v1");
}
}
+32
View File
@@ -0,0 +1,32 @@
using CannedNet.Data;
using Microsoft.EntityFrameworkCore;
namespace CannedNet.Services;
public static class ServiceExtensions
{
public static void ConfigureRecNetServices(this WebApplicationBuilder builder)
{
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.PropertyNamingPolicy = null);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres";
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));
builder.Services.AddScoped<JwtTokenService>();
builder.Services.AddSingleton<NotificationService>();
builder.Services.AddSignalR();
}
public static void ConfigureHostUrl(this WebApplicationBuilder builder, string defaultUrl)
{
var url = builder.Configuration["Hosting:Url"];
builder.WebHost.UseUrls(string.IsNullOrWhiteSpace(url) ? defaultUrl : url);
}
public static WebApplicationBuilder CreateRecNetBuilder(string[]? args) =>
WebApplication.CreateBuilder(args ?? []);
}
+24
View File
@@ -0,0 +1,24 @@
namespace CannedNet.Services;
public class ServiceRegistry
{
public required string Name { get; init; }
public required int Port { get; init; }
public required Type ServiceType { get; init; }
public required Action<WebApplicationBuilder> ConfigureBuilder { get; init; }
public required Action<WebApplication, JwtTokenService> MapEndpoints { get; init; }
}
public static class Services
{
public static readonly List<ServiceRegistry> All =
[
new() { Name = "NS", Port = 5001, ServiceType = typeof(NSService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NSService().MapEndpoints(app) },
new() { Name = "API", Port = 5000, ServiceType = typeof(APIService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new APIService().MapEndpoints(app) },
new() { Name = "Auth", Port = 5002, ServiceType = typeof(AuthService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AuthService().MapEndpoints(app) },
new() { Name = "Chat", Port = 5003, ServiceType = typeof(ChatService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new ChatService().MapEndpoints(app) },
new() { Name = "Match", Port = 5004, ServiceType = typeof(MatchmakingService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new MatchmakingService().MapEndpoints(app) },
new() { Name = "Accounts", Port = 5005, ServiceType = typeof(AccountsService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, jwt) => new AccountsService().MapEndpoints(app, jwt) },
new() { Name = "Notify", Port = 5006, ServiceType = typeof(NotifyService), ConfigureBuilder = b => b.ConfigureRecNetServices(), MapEndpoints = (app, _) => new NotifyService().MapEndpoints(app) }
];
}
@@ -2,6 +2,10 @@
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres" "DefaultConnection": "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres"
}, },
"Ports": {
"API": 5000,
"NS": 5001
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information" "Default": "Information"
@@ -6,7 +6,11 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=game.db;Cache=Shared" "DefaultConnection": "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres"
},
"Ports": {
"Primary": 5000,
"Secondary": 5001
}, },
"AllowedHosts": "*" "AllowedHosts": "*"
} }