1
0
mirror of synced 2024-09-23 18:48:22 +02:00

Start migration to asp .net core + entity framework core

This commit is contained in:
asesidaa 2023-02-08 21:33:22 +08:00
parent aea09c2c8b
commit f7761f200f
56 changed files with 2798 additions and 0 deletions

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Riok.Mapperly" Version="2.7.0-next.2" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
using System.Reflection;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Application;
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;
}
}

View File

@ -0,0 +1,33 @@
using System.Xml.Serialization;
namespace Application.Dto;
public class CardDto
{
[XmlElement(ElementName = "card_id")]
public long CardId { get; set; }
[XmlElement(ElementName = "player_name")]
public string PlayerName { get; set; } = string.Empty;
[XmlElement("score_i1")]
public long ScoreI1 { get; set; }
[XmlElement("fcol1")]
public long Fcol1 { get; set; }
[XmlElement("fcol2")]
public long Fcol2 { get; set; }
[XmlElement("fcol3")]
public long Fcol3 { get; set; }
[XmlElement("achieve_status")]
public string AchieveStatus { get; set; } = string.Empty;
[XmlElement("created")]
public string Created { get; set; } = string.Empty;
[XmlElement("modified")]
public string Modified { get; set; } = string.Empty;
}

View File

@ -0,0 +1,62 @@
using Application.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Application.Game.Option;
public record PlayCountQuery(long CardId) : IRequest<long>;
public class PlayCountQueryHandler : IRequestHandler<PlayCountQuery, long>
{
private readonly ICardDbContext context;
private readonly ILogger<PlayCountQueryHandler> logger;
public PlayCountQueryHandler(ICardDbContext context, ILogger<PlayCountQueryHandler> logger)
{
this.context = context;
this.logger = logger;
}
public async Task<long> Handle(PlayCountQuery request, CancellationToken cancellationToken)
{
return await GetPlayCount(request.CardId);
}
private async Task<long> GetPlayCount(long cardId)
{
var record = await context.CardPlayCounts.FirstOrDefaultAsync(count => count.CardId == cardId);
if (record is null)
{
return 0;
}
var now = DateTime.Now;
var lastPlayedTime = record.LastPlayedTime;
if (now <= lastPlayedTime)
{
logger.LogWarning("Clock skew detected! " +
"Current time: {Now}," +
"Last Play Time: {Last}", now, lastPlayedTime);
return 0;
}
DateTime start;
DateTime end;
if (now.Hour >= 8)
{
start = DateTime.Today.AddHours(8);
end = start.AddHours(24);
}
else
{
end = DateTime.Today.AddHours(8);
start = end.AddHours(-24);
}
var inBetween = lastPlayedTime >= start && lastPlayedTime <= end;
return inBetween ? record.PlayCount : 0;
}
}

View File

@ -0,0 +1,100 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Application.Interfaces;
using Domain.Config;
using MediatR;
using Microsoft.Extensions.Options;
namespace Application.Game.Server;
public record CertifyCommand(string? Gid, string? Mac, string? Random, string? Md5, string Host) : IRequest<string>;
public partial class CertifyCommandHandler : IRequestHandler<CertifyCommand, string>
{
private readonly RelayConfig relayConfig;
public CertifyCommandHandler(IOptions<RelayConfig> relayOptions)
{
relayConfig = relayOptions.Value;
}
public Task<string> Handle(CertifyCommand request, CancellationToken cancellationToken)
{
if (request.Gid == null)
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorNoGid));
}
if (request.Mac == null)
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorNoMac));
}
if (request.Random == null)
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorNoRandom));
}
if (request.Md5 == null)
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorNoHash));
}
if (!MacValid(request.Mac))
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorInvalidMac));
}
if (!Md5Valid(request.Md5))
{
return Task.FromResult(QuitWithError(ErrorCode.ErrorInvalidHash));
}
var ticket = string.Join(string.Empty,
MD5.HashData(Encoding.UTF8.GetBytes(request.Gid)).Select(b => b.ToString("x2")));
var response = $"host=card_id=7020392000147361,relay_addr={relayConfig.RelayServer},relay_port={relayConfig.RelayPort}\n" +
"no=1337\n" +
"name=123\n" +
"pref=nesys\n" +
"addr=nesys@home\n" +
"x-next-time=15\n" +
$"x-img=http://{request.Host}/news.png\n" +
$"x-ranking=http://{request.Host}/ranking/ranking.php\n" +
$"ticket={ticket}";
return Task.FromResult(response);
}
private static bool MacValid(string mac)
{
return MacRegex().IsMatch(mac);
}
private static bool Md5Valid(string md5)
{
return Md5Regex().IsMatch(md5);
}
private static string QuitWithError(ErrorCode errorCode)
{
return $"error={(int)errorCode}";
}
private enum ErrorCode
{
ErrorNoGid,
ErrorNoMac,
ErrorNoRandom,
ErrorNoHash,
ErrorInvalidMac,
ErrorInvalidHash
}
[GeneratedRegex("^[a-fA-F0-9]{12}$")]
private static partial Regex MacRegex();
[GeneratedRegex("^[a-fA-F0-9]{32}$")]
private static partial Regex Md5Regex();
}

View File

@ -0,0 +1,48 @@
using System.Text;
using Application.Interfaces;
using MediatR;
namespace Application.Game.Server;
public record GetDataQuery(string Host) : IRequest<string>;
public class GetDataQueryHandler : IRequestHandler<GetDataQuery, string>
{
private readonly IEventManagerService eventManagerService;
public GetDataQueryHandler(IEventManagerService eventManagerService)
{
this.eventManagerService = eventManagerService;
}
public Task<string> Handle(GetDataQuery request, CancellationToken cancellationToken)
{
var response = "count=0\n" +
"nexttime=0\n";
if (!eventManagerService.UseEvents())
{
return Task.FromResult(response);
}
var host = request.Host;
var urlBase = $"http://{host}/events/";
var dataString = new StringBuilder();
var events = eventManagerService.GetEvents();
var count = 0;
foreach (var pair in events.Select((@event, i) => new {Value = @event, Index = i}))
{
var value = pair.Value;
var index = pair.Index;
var fileUrl = $"{urlBase}{value.Name}";
var eventString = $"{index},{fileUrl},{value.NotBefore},{value.NotAfter},{value.Md5},{value.Index}";
dataString.Append(eventString).Append('\n');
count++;
}
response = $"count={count}\n" +
"nexttime=1\n" +
$"{dataString}";
return Task.FromResult(response);
}
}

View File

@ -0,0 +1,19 @@
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Application.Interfaces;
public interface ICardDbContext
{
public DbSet<CardBdatum> CardBdata { get; set; }
public DbSet<CardDetail> CardDetails { get; set; }
public DbSet<CardMain> CardMains { get; set; }
public DbSet<CardPlayCount> CardPlayCounts { get; set; }
public Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,12 @@
using Domain.Models;
namespace Application.Interfaces;
public interface IEventManagerService
{
public void InitializeEvents();
public bool UseEvents();
public IEnumerable<Event> GetEvents();
}

View File

@ -0,0 +1,13 @@
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Application.Interfaces;
public interface IMusicDbContext
{
public DbSet<MusicAou> MusicAous { get; set; }
public DbSet<MusicExtra> MusicExtras { get; set; }
public DbSet<MusicUnlock> MusicUnlocks { get; set; }
}

View File

@ -0,0 +1,11 @@
using Application.Dto;
using Domain.Entities;
using Riok.Mapperly.Abstractions;
namespace Application.Mappers;
[Mapper]
public static partial class CardMapper
{
public static partial CardDto CardMainToCardDto(this CardMain cardMain);
}

View File

@ -0,0 +1,17 @@
namespace Domain.Config;
public class EventConfig
{
public const string EVENT_SECTION = "Events";
public bool UseEvents { get; set; }
public List<EventFile> EventFiles { get; set; } = new();
}
public class EventFile
{
public string FileName { get; set; } = string.Empty;
public int Index { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace Domain.Config;
public class RelayConfig
{
public const string RELAY_SECTION = "Relay";
public string RelayServer { get; set; } = "127.0.0.1";
public int RelayPort { get; set; } = 3333;
}

9
Domain/Domain.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,10 @@
namespace Domain.Entities;
public partial class CardBdatum
{
public long CardId { get; set; }
public string? Bdata { get; set; }
public long BdataSize { get; set; }
}

View File

@ -0,0 +1,38 @@
namespace Domain.Entities;
public partial class CardDetail
{
public long CardId { get; set; }
public long Pcol1 { get; set; }
public long Pcol2 { get; set; }
public long Pcol3 { get; set; }
public long ScoreI1 { get; set; }
public long ScoreUi1 { get; set; }
public long ScoreUi2 { get; set; }
public long ScoreUi3 { get; set; }
public long ScoreUi4 { get; set; }
public long ScoreUi5 { get; set; }
public long ScoreUi6 { get; set; }
public long ScoreBi1 { get; set; }
public string? LastPlayTenpoId { get; set; } = string.Empty;
public long Fcol1 { get; set; }
public long Fcol2 { get; set; }
public long Fcol3 { get; set; }
public DateTime LastPlayTime { get; set; }
}

View File

@ -0,0 +1,22 @@
namespace Domain.Entities;
public partial class CardMain
{
public long CardId { get; set; }
public string PlayerName { get; set; } = string.Empty;
public long ScoreI1 { get; set; }
public long Fcol1 { get; set; }
public long Fcol2 { get; set; }
public long Fcol3 { get; set; }
public string AchieveStatus { get; set; } = string.Empty;
public string? Created { get; set; } = string.Empty;
public string? Modified { get; set; } = string.Empty;
}

View File

@ -0,0 +1,10 @@
namespace Domain.Entities;
public partial class CardPlayCount
{
public long CardId { get; set; }
public long PlayCount { get; set; }
public DateTime LastPlayedTime { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Domain.Entities;
public class MusicAou
{
public long MusicId { get; set; }
public bool UseFlag { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Domain.Entities;
public class MusicExtra
{
public long MusicId { get; set; }
public bool UseFlag { get; set; }
}

View File

@ -0,0 +1,18 @@
namespace Domain.Entities;
public partial class MusicUnlock
{
public long MusicId { get; set; }
public string Title { get; set; } = string.Empty;
public string Artist { get; set; } = string.Empty;
public DateTime ReleaseDate { get; set; }
public DateTime EndDate { get; set; }
public bool NewFlag { get; set; }
public bool UseFlag { get; set; }
}

14
Domain/Models/Event.cs Normal file
View File

@ -0,0 +1,14 @@
namespace Domain.Models;
public class Event
{
public string Name { get; set; } = string.Empty;
public string Md5 { get; set; } = string.Empty;
public int Index { get; set; }
public string NotBefore { get; set; } = string.Empty;
public string NotAfter { get; set; } = string.Empty;
}

View File

@ -11,6 +11,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MudAdmin", "MudAdmin\MudAdm
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCRelayServer", "GCRelayServer\GCRelayServer.csproj", "{268178DF-6345-4D9E-A389-9E809CBF39C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatchServer", "MatchServer\MatchServer.csproj", "{CB91C3D3-ED69-4DC6-A205-B262A08BEE49}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainServer", "MainServer\MainServer.csproj", "{B8C6CA7E-5E58-43BC-8E03-84306916DE39}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{B0691233-0D7E-4694-8923-646E7A3BDBF4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{CB95F4B7-7627-4CCA-A2B6-2D6FD48446C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{2666D734-0E81-431E-A22D-216218FC9023}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,6 +43,26 @@ Global
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.Build.0 = Release|Any CPU
{CB91C3D3-ED69-4DC6-A205-B262A08BEE49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB91C3D3-ED69-4DC6-A205-B262A08BEE49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB91C3D3-ED69-4DC6-A205-B262A08BEE49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB91C3D3-ED69-4DC6-A205-B262A08BEE49}.Release|Any CPU.Build.0 = Release|Any CPU
{B8C6CA7E-5E58-43BC-8E03-84306916DE39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8C6CA7E-5E58-43BC-8E03-84306916DE39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8C6CA7E-5E58-43BC-8E03-84306916DE39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8C6CA7E-5E58-43BC-8E03-84306916DE39}.Release|Any CPU.Build.0 = Release|Any CPU
{B0691233-0D7E-4694-8923-646E7A3BDBF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0691233-0D7E-4694-8923-646E7A3BDBF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0691233-0D7E-4694-8923-646E7A3BDBF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0691233-0D7E-4694-8923-646E7A3BDBF4}.Release|Any CPU.Build.0 = Release|Any CPU
{CB95F4B7-7627-4CCA-A2B6-2D6FD48446C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB95F4B7-7627-4CCA-A2B6-2D6FD48446C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB95F4B7-7627-4CCA-A2B6-2D6FD48446C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB95F4B7-7627-4CCA-A2B6-2D6FD48446C1}.Release|Any CPU.Build.0 = Release|Any CPU
{2666D734-0E81-431E-A22D-216218FC9023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2666D734-0E81-431E-A22D-216218FC9023}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2666D734-0E81-431E-A22D-216218FC9023}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2666D734-0E81-431E-A22D-216218FC9023}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=ADDR/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BDATA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcol/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,306 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using CertificateManager;
using CertificateManager.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Infrastructure.Common;
public class CertificateService
{
private const X509KeyUsageFlags ROOT_CA_X509_KEY_USAGE_FLAGS = X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.DataEncipherment |
X509KeyUsageFlags.KeyEncipherment |
X509KeyUsageFlags.DigitalSignature;
private const X509KeyStorageFlags X509_KEY_STORAGE_FLAGS_MACHINE = X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.Exportable;
private const X509KeyUsageFlags CERT_X509_KEY_USAGE_FLAGS = X509KeyUsageFlags.DataEncipherment |
X509KeyUsageFlags.KeyEncipherment |
X509KeyUsageFlags.DigitalSignature;
private const string ROOT_CA_CN = "Taito Arcade Machine CA";
private const string CERT_CN = "GC local server";
private const string CERT_DIR = "Certificates";
private const string CERT_FILE_NAME = "cert.pfx";
private const string ROOT_CERT_FILE_NAME = "root.pfx";
private static readonly string CERT_PATH = Path.Combine(CERT_DIR, CERT_FILE_NAME);
private static readonly string ROOT_CERT_PATH = Path.Combine(CERT_DIR, ROOT_CERT_FILE_NAME);
private ILogger logger;
private static readonly DistinguishedName ROOT_CA_DISTINGUISHED_NAME = new()
{
CommonName = ROOT_CA_CN
};
private static readonly DistinguishedName CERT_DISTINGUISHED_NAME = new()
{
CommonName = CERT_CN
};
private static readonly BasicConstraints ROOT_CA_BASIC_CONSTRAINTS = new()
{
CertificateAuthority = true,
HasPathLengthConstraint = true,
PathLengthConstraint = 3,
Critical = true
};
public static readonly BasicConstraints CERT_BASIC_CONSTRAINTS = new()
{
CertificateAuthority = false,
HasPathLengthConstraint = false,
PathLengthConstraint = 0,
Critical = true,
};
private readonly SubjectAlternativeName subjectAlternativeName;
private static readonly ValidityPeriod VALIDITY_PERIOD = new()
{
ValidFrom = DateTime.UtcNow,
ValidTo = DateTime.UtcNow.AddYears(3)
};
private static readonly OidCollection OID_COLLECTION = new()
{
OidLookup.ServerAuthentication,
OidLookup.AnyPurpose
};
public CertificateService(string serverIp, ILogger logger)
{
this.logger = logger;
subjectAlternativeName = new SubjectAlternativeName
{
DnsName = new List<string>
{
"localhost",
"cert.nesys.jp",
"nesys.taito.co.jp",
"fjm170920zero.nesica.net"
},
IpAddress = System.Net.IPAddress.Parse(serverIp)
};
}
public X509Certificate2 InitializeCertificate()
{
return Environment.OSVersion.Platform == PlatformID.Win32NT
? InitializeCertificateWindows()
: InitializeCertificateOthers();
}
private X509Certificate2 InitializeCertificateOthers()
{
if (CertificateExists())
{
return new X509Certificate2(CERT_PATH);
}
logger.LogInformation("Existing certs not found! Removing old certificates and genrate new ones...");
File.Delete(CERT_PATH);
File.Delete(ROOT_CERT_PATH);
return GenerateCertificate();
}
private X509Certificate2 InitializeCertificateWindows()
{
if (CertificateExists())
{
var existingCert = GetCertificate(StoreName.My, StoreLocation.LocalMachine, CERT_CN);
if (existingCert != null)
{
return existingCert;
}
logger.LogInformation("Existing CN not found! Removing old certificates and genrate new ones...");
}
RemovePreviousCert(StoreName.My, StoreLocation.LocalMachine);
RemovePreviousCert(StoreName.Root, StoreLocation.LocalMachine);
return GenerateCertificate();
}
private X509Certificate2 GenerateCertificate()
{
var serviceProvider = new ServiceCollection()
.AddCertificateManager().BuildServiceProvider();
var createCertificates = serviceProvider.GetService<CreateCertificates>();
if (createCertificates == null)
{
logger.LogError("Cannot initialize CreateCertificates service!");
throw new Exception();
}
var rootCa = createCertificates.NewRsaSelfSignedCertificate(
ROOT_CA_DISTINGUISHED_NAME,
ROOT_CA_BASIC_CONSTRAINTS,
VALIDITY_PERIOD,
new SubjectAlternativeName(),
OID_COLLECTION,
ROOT_CA_X509_KEY_USAGE_FLAGS,
new RsaConfiguration()
);
var cert = createCertificates.NewRsaChainedCertificate(
CERT_DISTINGUISHED_NAME,
CERT_BASIC_CONSTRAINTS,
VALIDITY_PERIOD,
subjectAlternativeName,
rootCa,
OID_COLLECTION,
CERT_X509_KEY_USAGE_FLAGS,
new RsaConfiguration()
);
var exportService = serviceProvider.GetService<ImportExportCertificate>();
if (exportService == null)
{
logger.LogError("Cannot initialize ImportExportCertificate service!");
throw new Exception();
}
var rootCaPfxBytes = exportService.ExportRootPfx(null, rootCa);
var certPfxBytes = exportService.ExportChainedCertificatePfx(null, cert, rootCa);
var rootCaWithPrivateKey = new X509Certificate2(rootCaPfxBytes, (string)null!,
X509_KEY_STORAGE_FLAGS_MACHINE);
var certWithPrivateKey = new X509Certificate2(certPfxBytes, (string)null!,
X509_KEY_STORAGE_FLAGS_MACHINE);
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
AddCertToStore(rootCaWithPrivateKey, StoreName.My, StoreLocation.LocalMachine);
AddCertToStore(rootCaWithPrivateKey, StoreName.Root, StoreLocation.LocalMachine);
AddCertToStore(certWithPrivateKey, StoreName.My, StoreLocation.LocalMachine);
}
Directory.CreateDirectory(CERT_DIR);
File.WriteAllBytes(ROOT_CERT_PATH, rootCaWithPrivateKey.Export(X509ContentType.Pfx));
File.WriteAllBytes(CERT_PATH, certWithPrivateKey.Export(X509ContentType.Pfx));
return certWithPrivateKey;
}
private void AddCertToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation)
{
try
{
var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
store.Close();
}
catch (Exception e)
{
logger.LogError(e, "An exception occurs when adding certificate");
}
}
private void RemovePreviousCert(StoreName storeName, StoreLocation storeLocation)
{
try
{
var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadWrite);
var result = store.Certificates.Find(X509FindType.FindByIssuerName, ROOT_CA_CN, true);
if (result.Any())
{
store.RemoveRange(result);
logger.LogInformation("Removed previous certificates!");
}
store.Close();
}
catch (Exception e)
{
logger.LogError(e, "An exception occurs when removing previous certificates");
}
}
private bool CertificateExists()
{
bool certificateExists;
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
// Non windows just use generated certificate file
certificateExists = Path.Exists(Path.Combine(CERT_DIR, "cert.pfx")) &&
Path.Exists(Path.Combine(CERT_DIR, "root.pfx"));
}
else
{
try
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var result = store.Certificates.Find(X509FindType.FindByIssuerName, ROOT_CA_CN, true);
certificateExists = result.Count == 2;
store.Close();
}
catch (Exception e)
{
logger.LogError(e, "An exception occurs when checking certificates");
return false;
}
}
if (certificateExists)
{
logger.LogInformation("Certificate exists!");
}
else
{
logger.LogInformation("Certificate not found! Will generate new certs...");
}
return certificateExists;
}
private X509Certificate2? GetCertificate(StoreName storeName, StoreLocation storeLocation, string commonName)
{
try
{
var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadWrite);
var result = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName,
$"CN={commonName}", true);
if (result.Any())
{
logger.LogInformation("Certificate CN={CommonName} found!", commonName);
return result.First();
}
store.Close();
return null;
}
catch (Exception e)
{
logger.LogError(e, "An exception occurs when getting certificate {CommonName}", commonName);
return null;
}
}
}

View File

@ -0,0 +1,31 @@
using System.Diagnostics;
using Validation;
namespace Infrastructure.Common;
public static class PathHelper
{
public static string DatabasePath = Path.Combine(BasePath, "Database");
public static string ConfigurationPath = Path.Combine(BasePath, "Configurations");
public static string BasePath
{
get
{
var assemblyPath = Environment.ProcessPath;
Assumes.NotNull(assemblyPath);
#if DEBUG
var parentFullName = Directory.GetParent(assemblyPath)?.Parent?.Parent?.Parent?.FullName;
return parentFullName ?? "";
#else
var parent = Directory.GetParent(assemblyPath);
Assumes.NotNull(parent);
return parent.ToString();
#endif
}
}
}

View File

@ -0,0 +1,46 @@
using Application.Interfaces;
using Infrastructure.Common;
using Infrastructure.Persistence;
using Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure;
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IEventManagerService, EventManagerService>();
services.AddDbContext<MusicDbContext>(option =>
{
var dbName = configuration["MusicDbName"];
if (string.IsNullOrEmpty(dbName))
{
dbName = "music471omni.db3";
}
var path = Path.Combine(PathHelper.DatabasePath, dbName);
option.UseSqlite($"Data Source={path}");
});
services.AddDbContext<CardDbContext>(option =>
{
var dbName = configuration["CardDbName"];
if (string.IsNullOrEmpty(dbName))
{
dbName = "card.db3";
}
var path = Path.Combine(PathHelper.DatabasePath, dbName);
option.UseSqlite($"Data Source={path}");
});
services.AddScoped<ICardDbContext>(provider => provider.GetService<CardDbContext>() ?? throw new InvalidOperationException());
services.AddScoped<IMusicDbContext>(provider => provider.GetService<MusicDbContext>() ?? throw new InvalidOperationException());
return services;
}
}

View File

@ -0,0 +1,9 @@
namespace Infrastructure.Exceptions;
public class EventFileNotFoundException : Exception
{
}
public class EventFileTypeUnknownException : Exception
{
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CertificateManager" Version="1.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
<PackageReference Include="Validation" Version="2.6.13-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,182 @@
// <auto-generated />
using Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Infrastructure.Migrations
{
[DbContext(typeof(CardDbContext))]
[Migration("20230208132952_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
modelBuilder.Entity("Domain.Entities.CardBdatum", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<string>("Bdata")
.HasColumnType("TEXT")
.HasColumnName("bdata");
b.Property<long>("BdataSize")
.HasColumnType("INTEGER")
.HasColumnName("bdata_size");
b.HasKey("CardId");
b.ToTable("card_bdata", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardDetail", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<long>("Pcol1")
.HasColumnType("INTEGER")
.HasColumnName("pcol1");
b.Property<long>("Pcol2")
.HasColumnType("INTEGER")
.HasColumnName("pcol2");
b.Property<long>("Pcol3")
.HasColumnType("INTEGER")
.HasColumnName("pcol3");
b.Property<long>("Fcol1")
.HasColumnType("INTEGER")
.HasColumnName("fcol1");
b.Property<long>("Fcol2")
.HasColumnType("INTEGER")
.HasColumnName("fcol2");
b.Property<long>("Fcol3")
.HasColumnType("INTEGER")
.HasColumnName("fcol3");
b.Property<string>("LastPlayTenpoId")
.HasColumnType("TEXT")
.HasColumnName("last_play_tenpo_id");
b.Property<long>("LastPlayTime")
.HasColumnType("INTEGER")
.HasColumnName("last_play_time");
b.Property<long>("ScoreBi1")
.HasColumnType("INTEGER")
.HasColumnName("score_bi1");
b.Property<long>("ScoreI1")
.HasColumnType("INTEGER")
.HasColumnName("score_i1");
b.Property<long>("ScoreUi1")
.HasColumnType("INTEGER")
.HasColumnName("score_ui1");
b.Property<long>("ScoreUi2")
.HasColumnType("INTEGER")
.HasColumnName("score_ui2");
b.Property<long>("ScoreUi3")
.HasColumnType("INTEGER")
.HasColumnName("score_ui3");
b.Property<long>("ScoreUi4")
.HasColumnType("INTEGER")
.HasColumnName("score_ui4");
b.Property<long>("ScoreUi5")
.HasColumnType("INTEGER")
.HasColumnName("score_ui5");
b.Property<long>("ScoreUi6")
.HasColumnType("INTEGER")
.HasColumnName("score_ui6");
b.HasKey("CardId", "Pcol1", "Pcol2", "Pcol3");
b.ToTable("card_detail", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardMain", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<string>("AchieveStatus")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("achieve_status");
b.Property<string>("Created")
.HasColumnType("TEXT")
.HasColumnName("created");
b.Property<long>("Fcol1")
.HasColumnType("INTEGER")
.HasColumnName("fcol1");
b.Property<long>("Fcol2")
.HasColumnType("INTEGER")
.HasColumnName("fcol2");
b.Property<long>("Fcol3")
.HasColumnType("INTEGER")
.HasColumnName("fcol3");
b.Property<string>("Modified")
.HasColumnType("TEXT")
.HasColumnName("modified");
b.Property<string>("PlayerName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("player_name");
b.Property<long>("ScoreI1")
.HasColumnType("INTEGER")
.HasColumnName("score_i1");
b.HasKey("CardId");
b.ToTable("card_main", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardPlayCount", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<long>("LastPlayedTime")
.HasColumnType("INTEGER")
.HasColumnName("last_played_time");
b.Property<long>("PlayCount")
.HasColumnType("INTEGER")
.HasColumnName("play_count");
b.HasKey("CardId");
b.ToTable("CardPlayCount", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,102 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Infrastructure.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "card_bdata",
columns: table => new
{
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
bdata = table.Column<string>(type: "TEXT", nullable: true),
bdatasize = table.Column<long>(name: "bdata_size", type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_card_bdata", x => x.cardid);
});
migrationBuilder.CreateTable(
name: "card_detail",
columns: table => new
{
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
pcol1 = table.Column<long>(type: "INTEGER", nullable: false),
pcol2 = table.Column<long>(type: "INTEGER", nullable: false),
pcol3 = table.Column<long>(type: "INTEGER", nullable: false),
scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false),
scoreui1 = table.Column<long>(name: "score_ui1", type: "INTEGER", nullable: false),
scoreui2 = table.Column<long>(name: "score_ui2", type: "INTEGER", nullable: false),
scoreui3 = table.Column<long>(name: "score_ui3", type: "INTEGER", nullable: false),
scoreui4 = table.Column<long>(name: "score_ui4", type: "INTEGER", nullable: false),
scoreui5 = table.Column<long>(name: "score_ui5", type: "INTEGER", nullable: false),
scoreui6 = table.Column<long>(name: "score_ui6", type: "INTEGER", nullable: false),
scorebi1 = table.Column<long>(name: "score_bi1", type: "INTEGER", nullable: false),
lastplaytenpoid = table.Column<string>(name: "last_play_tenpo_id", type: "TEXT", nullable: true),
fcol1 = table.Column<long>(type: "INTEGER", nullable: false),
fcol2 = table.Column<long>(type: "INTEGER", nullable: false),
fcol3 = table.Column<long>(type: "INTEGER", nullable: false),
lastplaytime = table.Column<long>(name: "last_play_time", type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_card_detail", x => new { x.cardid, x.pcol1, x.pcol2, x.pcol3 });
});
migrationBuilder.CreateTable(
name: "card_main",
columns: table => new
{
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
playername = table.Column<string>(name: "player_name", type: "TEXT", nullable: false),
scorei1 = table.Column<long>(name: "score_i1", type: "INTEGER", nullable: false),
fcol1 = table.Column<long>(type: "INTEGER", nullable: false),
fcol2 = table.Column<long>(type: "INTEGER", nullable: false),
fcol3 = table.Column<long>(type: "INTEGER", nullable: false),
achievestatus = table.Column<string>(name: "achieve_status", type: "TEXT", nullable: false),
created = table.Column<string>(type: "TEXT", nullable: true),
modified = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_card_main", x => x.cardid);
});
migrationBuilder.CreateTable(
name: "CardPlayCount",
columns: table => new
{
cardid = table.Column<long>(name: "card_id", type: "INTEGER", nullable: false),
playcount = table.Column<long>(name: "play_count", type: "INTEGER", nullable: false),
lastplayedtime = table.Column<long>(name: "last_played_time", type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CardPlayCount", x => x.cardid);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "card_bdata");
migrationBuilder.DropTable(
name: "card_detail");
migrationBuilder.DropTable(
name: "card_main");
migrationBuilder.DropTable(
name: "CardPlayCount");
}
}
}

View File

@ -0,0 +1,179 @@
// <auto-generated />
using Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Infrastructure.Migrations
{
[DbContext(typeof(CardDbContext))]
partial class CardDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
modelBuilder.Entity("Domain.Entities.CardBdatum", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<string>("Bdata")
.HasColumnType("TEXT")
.HasColumnName("bdata");
b.Property<long>("BdataSize")
.HasColumnType("INTEGER")
.HasColumnName("bdata_size");
b.HasKey("CardId");
b.ToTable("card_bdata", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardDetail", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<long>("Pcol1")
.HasColumnType("INTEGER")
.HasColumnName("pcol1");
b.Property<long>("Pcol2")
.HasColumnType("INTEGER")
.HasColumnName("pcol2");
b.Property<long>("Pcol3")
.HasColumnType("INTEGER")
.HasColumnName("pcol3");
b.Property<long>("Fcol1")
.HasColumnType("INTEGER")
.HasColumnName("fcol1");
b.Property<long>("Fcol2")
.HasColumnType("INTEGER")
.HasColumnName("fcol2");
b.Property<long>("Fcol3")
.HasColumnType("INTEGER")
.HasColumnName("fcol3");
b.Property<string>("LastPlayTenpoId")
.HasColumnType("TEXT")
.HasColumnName("last_play_tenpo_id");
b.Property<long>("LastPlayTime")
.HasColumnType("INTEGER")
.HasColumnName("last_play_time");
b.Property<long>("ScoreBi1")
.HasColumnType("INTEGER")
.HasColumnName("score_bi1");
b.Property<long>("ScoreI1")
.HasColumnType("INTEGER")
.HasColumnName("score_i1");
b.Property<long>("ScoreUi1")
.HasColumnType("INTEGER")
.HasColumnName("score_ui1");
b.Property<long>("ScoreUi2")
.HasColumnType("INTEGER")
.HasColumnName("score_ui2");
b.Property<long>("ScoreUi3")
.HasColumnType("INTEGER")
.HasColumnName("score_ui3");
b.Property<long>("ScoreUi4")
.HasColumnType("INTEGER")
.HasColumnName("score_ui4");
b.Property<long>("ScoreUi5")
.HasColumnType("INTEGER")
.HasColumnName("score_ui5");
b.Property<long>("ScoreUi6")
.HasColumnType("INTEGER")
.HasColumnName("score_ui6");
b.HasKey("CardId", "Pcol1", "Pcol2", "Pcol3");
b.ToTable("card_detail", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardMain", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<string>("AchieveStatus")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("achieve_status");
b.Property<string>("Created")
.HasColumnType("TEXT")
.HasColumnName("created");
b.Property<long>("Fcol1")
.HasColumnType("INTEGER")
.HasColumnName("fcol1");
b.Property<long>("Fcol2")
.HasColumnType("INTEGER")
.HasColumnName("fcol2");
b.Property<long>("Fcol3")
.HasColumnType("INTEGER")
.HasColumnName("fcol3");
b.Property<string>("Modified")
.HasColumnType("TEXT")
.HasColumnName("modified");
b.Property<string>("PlayerName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("player_name");
b.Property<long>("ScoreI1")
.HasColumnType("INTEGER")
.HasColumnName("score_i1");
b.HasKey("CardId");
b.ToTable("card_main", (string)null);
});
modelBuilder.Entity("Domain.Entities.CardPlayCount", b =>
{
b.Property<long>("CardId")
.HasColumnType("INTEGER")
.HasColumnName("card_id");
b.Property<long>("LastPlayedTime")
.HasColumnType("INTEGER")
.HasColumnName("last_played_time");
b.Property<long>("PlayCount")
.HasColumnType("INTEGER")
.HasColumnName("play_count");
b.HasKey("CardId");
b.ToTable("CardPlayCount", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,34 @@
using System.Data;
using Infrastructure.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace Infrastructure.Migrations;
public static class MigrationHelper
{
public static bool Exists(string tableName)
{
var options = new DbContextOptionsBuilder().UseSqlite($"Data Source={GetConnectionString()}");
using var context = new DbContext(options.Options);
using var command = context.Database.GetDbConnection().CreateCommand();
command.CommandText = $"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='{tableName}';";
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using var reader = command.ExecuteReader();
return reader.Read()? (long)reader[0] == 1 : false;
}
public static string GetConnectionString()
{
var builder = new ConfigurationBuilder()
.SetBasePath(PathHelper.ConfigurationPath)
.AddJsonFile("database.json", optional: false, reloadOnChange: false);
var cardDbName = builder.Build()["CardDbName"];
var cardDbPath = Path.Combine(PathHelper.DatabasePath, cardDbName);
return cardDbPath;
}
}

View File

@ -0,0 +1,118 @@
using Application.Interfaces;
using Domain.Entities;
using Infrastructure.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Infrastructure.Persistence;
public partial class CardDbContext : DbContext, ICardDbContext
{
public CardDbContext()
{
}
public CardDbContext(DbContextOptions<CardDbContext> options)
: base(options)
{
}
public virtual DbSet<CardBdatum> CardBdata { get; set; } = null!;
public virtual DbSet<CardDetail> CardDetails { get; set; } = null!;
public virtual DbSet<CardMain> CardMains { get; set; } = null!;
public virtual DbSet<CardPlayCount> CardPlayCounts { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
return;
}
var defaultDb = Path.Combine(PathHelper.DatabasePath, "card.db3");
optionsBuilder.UseSqlite($"Data Source={defaultDb}");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CardBdatum>(entity =>
{
entity.HasKey(e => e.CardId);
entity.ToTable("card_bdata");
entity.Property(e => e.CardId)
.ValueGeneratedNever()
.HasColumnName("card_id");
entity.Property(e => e.Bdata).HasColumnName("bdata");
entity.Property(e => e.BdataSize).HasColumnName("bdata_size");
});
modelBuilder.Entity<CardDetail>(entity =>
{
entity.HasKey(e => new { e.CardId, e.Pcol1, e.Pcol2, e.Pcol3 });
entity.ToTable("card_detail");
entity.Property(e => e.CardId).HasColumnName("card_id");
entity.Property(e => e.Pcol1).HasColumnName("pcol1");
entity.Property(e => e.Pcol2).HasColumnName("pcol2");
entity.Property(e => e.Pcol3).HasColumnName("pcol3");
entity.Property(e => e.Fcol1).HasColumnName("fcol1");
entity.Property(e => e.Fcol2).HasColumnName("fcol2");
entity.Property(e => e.Fcol3).HasColumnName("fcol3");
entity.Property(e => e.LastPlayTenpoId).HasColumnName("last_play_tenpo_id").IsRequired(false);
entity.Property(e => e.LastPlayTime).HasColumnName("last_play_time")
.HasConversion<DateTimeToTicksConverter>();
entity.Property(e => e.ScoreBi1).HasColumnName("score_bi1");
entity.Property(e => e.ScoreI1).HasColumnName("score_i1");
entity.Property(e => e.ScoreUi1).HasColumnName("score_ui1");
entity.Property(e => e.ScoreUi2).HasColumnName("score_ui2");
entity.Property(e => e.ScoreUi3).HasColumnName("score_ui3");
entity.Property(e => e.ScoreUi4).HasColumnName("score_ui4");
entity.Property(e => e.ScoreUi5).HasColumnName("score_ui5");
entity.Property(e => e.ScoreUi6).HasColumnName("score_ui6");
});
modelBuilder.Entity<CardMain>(entity =>
{
entity.HasKey(e => e.CardId);
entity.ToTable("card_main");
entity.Property(e => e.CardId)
.ValueGeneratedNever()
.HasColumnName("card_id");
entity.Property(e => e.AchieveStatus).HasColumnName("achieve_status");
entity.Property(e => e.Created).HasColumnName("created").IsRequired(false);
entity.Property(e => e.Fcol1).HasColumnName("fcol1");
entity.Property(e => e.Fcol2).HasColumnName("fcol2");
entity.Property(e => e.Fcol3).HasColumnName("fcol3");
entity.Property(e => e.Modified).HasColumnName("modified").IsRequired(false);
entity.Property(e => e.PlayerName).HasColumnName("player_name");
entity.Property(e => e.ScoreI1).HasColumnName("score_i1");
});
modelBuilder.Entity<CardPlayCount>(entity =>
{
entity.HasKey(e => e.CardId);
entity.ToTable("CardPlayCount");
entity.Property(e => e.CardId)
.ValueGeneratedNever()
.HasColumnName("card_id");
entity.Property(e => e.LastPlayedTime).HasColumnName("last_played_time")
.HasConversion<DateTimeToTicksConverter>();
entity.Property(e => e.PlayCount).HasColumnName("play_count");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@ -0,0 +1,82 @@
using Application.Interfaces;
using Domain.Entities;
using Infrastructure.Common;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Persistence;
public partial class MusicDbContext : DbContext, IMusicDbContext
{
public MusicDbContext()
{
}
public MusicDbContext(DbContextOptions<MusicDbContext> options)
: base(options)
{
}
public virtual DbSet<MusicAou> MusicAous { get; set; } = null!;
public virtual DbSet<MusicExtra> MusicExtras { get; set; } = null!;
public virtual DbSet<MusicUnlock> MusicUnlocks { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
{
return;
}
var defaultDb = Path.Combine(PathHelper.DatabasePath, "music471omni.db3");
optionsBuilder.UseSqlite($"Data Source={defaultDb}");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MusicAou>(entity =>
{
entity.HasKey(e => e.MusicId);
entity.ToTable("music_aou");
entity.Property(e => e.MusicId)
.ValueGeneratedNever()
.HasColumnName("music_id");
entity.Property(e => e.UseFlag).HasColumnName("use_flag");
});
modelBuilder.Entity<MusicExtra>(entity =>
{
entity.HasKey(e => e.MusicId);
entity.ToTable("music_extra");
entity.Property(e => e.MusicId)
.ValueGeneratedNever()
.HasColumnName("music_id");
entity.Property(e => e.UseFlag).HasColumnName("use_flag");
});
modelBuilder.Entity<MusicUnlock>(entity =>
{
entity.HasKey(e => e.MusicId);
entity.ToTable("music_unlock");
entity.Property(e => e.MusicId)
.ValueGeneratedNever()
.HasColumnName("music_id");
entity.Property(e => e.Artist).HasColumnName("artist");
entity.Property(e => e.Title).HasColumnName("title");
entity.Property(e => e.ReleaseDate).HasColumnName("release_date");
entity.Property(e => e.EndDate).HasColumnName("end_date");
entity.Property(e => e.NewFlag).HasColumnName("new_flag");
entity.Property(e => e.UseFlag).HasColumnName("use_flag");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@ -0,0 +1,160 @@
using System.Security.Cryptography;
using Application.Interfaces;
using Domain.Config;
using Domain.Models;
using Infrastructure.Exceptions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Infrastructure.Services;
public class EventManagerService : IEventManagerService
{
#region Constants
private const string WWWROOT = "wwwroot";
private const string EVENT_FOLDER = "events";
private static readonly DateTimeOffset NOT_BEFORE = new(new DateTime(2013, 1, 1));
private static readonly DateTimeOffset NOT_AFTER = NOT_BEFORE + new TimeSpan(360 * 20, 0, 0, 0);
private static readonly string NOT_BEFORE_STRING = NOT_BEFORE.ToUnixTimeSeconds().ToString();
private static readonly string NOT_AFTER_STRING = NOT_AFTER.ToUnixTimeSeconds().ToString();
#endregion
private readonly ILogger<EventManagerService> logger;
private readonly EventConfig config;
private readonly List<Event> events;
private readonly bool useEvents;
public EventManagerService(IOptions<EventConfig> config, ILogger<EventManagerService> logger)
{
this.logger = logger;
this.config = config.Value;
events = new List<Event>();
useEvents = this.config.UseEvents;
}
public bool UseEvents()
{
return useEvents;
}
public IEnumerable<Event> GetEvents()
{
return events;
}
public void InitializeEvents()
{
foreach (var eventData in config.EventFiles)
{
var filePath = Path.Combine(WWWROOT, EVENT_FOLDER, eventData.FileName);
if (!File.Exists(filePath))
{
logger.LogError("Event file {File} not found at path {Path}!", eventData.FileName,
filePath);
throw new EventFileNotFoundException();
}
var md5 = ComputeFileMd5(filePath);
var @event = new Event
{
Name = eventData.FileName,
Md5 = md5,
NotBefore = NOT_BEFORE_STRING,
NotAfter = NOT_AFTER_STRING
};
var eventType = DetermineFileType(eventData.FileName);
@event.Index = eventType switch
{
EventFileType.Event => 0,
EventFileType.EventRegPic => 1,
EventFileType.EventSgRegPic => 2,
EventFileType.NewsBigPic => eventData.Index,
EventFileType.NewsSmallPic => 1,
EventFileType.Telop => 0,
EventFileType.EventCmp => 8,
_ => throw new ArgumentOutOfRangeException()
};
events.Add(@event);
}
if (events.Exists(event1 => event1.Name.StartsWith("news_big_") && event1.Index == 0))
{
return;
}
logger.LogWarning("No big news image with index 0! Changing a random one...");
events.First(event1 => event1.Name.StartsWith("news_big_")).Index = 0;
}
private EventFileType DetermineFileType(string fileName)
{
if (fileName.EndsWith(".evt"))
{
return EventFileType.Event;
}
if (fileName.EndsWith(".cmp"))
{
return EventFileType.EventCmp;
}
if (fileName.EndsWith(".txt"))
{
return EventFileType.Telop;
}
if (fileName.Contains("_reg"))
{
return EventFileType.EventRegPic;
}
if (fileName.Contains("_sgreg"))
{
return EventFileType.EventSgRegPic;
}
if (fileName.StartsWith("news_big_"))
{
return EventFileType.NewsBigPic;
}
if (fileName.StartsWith("news_small_"))
{
return EventFileType.NewsSmallPic;
}
logger.LogError("Unknown event file type for file {File}", fileName);
throw new EventFileTypeUnknownException();
}
private static string ComputeFileMd5(string filePath)
{
using var file = File.OpenRead(filePath);
var hash = MD5.HashData(file);
var result = BitConverter.ToString(hash).Replace("-", "").ToLower();
return result;
}
private enum EventFileType
{
Event,
EventRegPic,
EventSgRegPic,
NewsBigPic,
NewsSmallPic,
Telop,
EventCmp
}
}

442
MainServer/.gitignore vendored Normal file
View File

@ -0,0 +1,442 @@
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### VisualStudio template
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# Ignore card db since we should start from scratch
/db/card.db3
Certificates
wwwroot/events/
Database/*

View File

@ -0,0 +1,4 @@
{
"CardDbName": "card.db3",
"MusicDbName": "music471omni.db3"
}

View File

@ -0,0 +1,39 @@
{
"Events": {
"UseEvents": true,
"EventFiles": [
{
"FileName": "event_103_20201125.evt",
"Index": 0
},
{
"FileName": "event_20201125_reg.jpg",
"Index": 1
},
{
"FileName": "event_20201125_sgreg.png",
"Index": 2
},
{
"FileName": "news_big_20201125_0.jpg",
"Index": 0
},
{
"FileName": "news_big_20201125_2.jpg",
"Index": 2
},
{
"FileName": "news_small_20201125_1.jpg",
"Index": 1
},
{
"FileName": "telop_20201125.txt",
"Index": 0
},
{
"FileName": "event_unlock_20201125.cmp",
"Index": 8
}
]
}
}

View File

@ -0,0 +1,15 @@
{
"AvatarCount": 356,
"NavigatorCount": 118,
"ItemCount": 21,
"SkinCount": 21,
"SeCount": 26,
"TitleCount": 5530,
"UnlockableSongIds":
[
11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492,
494, 498, 520, 548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622,
632, 659, 666, 668, 670, 672, 676, 680, 682, 685, 686, 697, 700, 701,
711, 720, 749, 875, 876, 877
]
}

View File

@ -0,0 +1,27 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "@mt = 'An unhandled exception has occurred while executing the request.'"
}
}
],
"WriteTo": [
{
"Name": "File",
"Args": { "path": "./Logs/log-.txt", "rollingInterval": "Day" }
}
]
}
}

View File

@ -0,0 +1,6 @@
{
"Relay": {
"RelayServer": "127.0.0.1",
"RelayPort": 3333
}
}

View File

@ -0,0 +1,3 @@
{
"ServerIp": "127.0.0.1"
}

View File

@ -0,0 +1,24 @@
using Domain.Entities;
using Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly MusicDbContext context;
public TestController(MusicDbContext context)
{
this.context = context;
}
[HttpGet]
public MusicUnlock GetOne()
{
return context.MusicUnlocks.First();
}
}
}

View File

@ -0,0 +1,15 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers;
public abstract class BaseController<T> : ControllerBase where T : BaseController<T>
{
private ILogger<T>? logger;
private ISender? mediator;
protected ISender Mediator => (mediator ??= HttpContext.RequestServices.GetService<ISender>()) ?? throw new InvalidOperationException();
protected ILogger<T> Logger => (logger ??= HttpContext.RequestServices.GetService<ILogger<T>>()) ?? throw new InvalidOperationException();
}

View File

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("alive")]
public class AliveController : ControllerBase
{
[HttpGet("i.php")]
public IActionResult AliveCheck()
{
var remoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress;
var serverIpAddress = Request.HttpContext.Connection.LocalIpAddress;
var response = $"REMOTE ADDRESS:{remoteIpAddress}\n" +
"SERVER NAME:GCLocalServer\n" +
$"SERVER ADDR:{serverIpAddress}";
return Ok(response);
}
[HttpGet("/{id}/Alive.txt")]
public IActionResult GetAliveFile()
{
return Ok("");
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("service/incom")]
public class IncomController : ControllerBase
{
private const string INCOM_RESPONSE = "1+1";
[HttpPost("incom.php")]
public IActionResult Incom()
{
return Ok(INCOM_RESPONSE);
}
[HttpPost("incomALL.php")]
public IActionResult IncomAll()
{
return Ok(INCOM_RESPONSE);
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("service/respone")]
public class ResponeController : ControllerBase
{
[HttpPost("respone.php")]
public IActionResult Respone()
{
return Ok("1");
}
}

View File

@ -0,0 +1,41 @@
using Application.Game.Server;
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("server")]
public class ServerController : BaseController<ServerController>
{
[HttpGet("cursel.php")]
public ActionResult<string> GetCursel()
{
return Ok("1\n");
}
[HttpGet("gameinfo.php")]
public ActionResult<string> GetGameInfo()
{
return Ok("0\n" +
"3\n" +
"301000,test1\n" +
"302000,test2\n" +
"303000,test3");
}
[HttpGet("certify.php")]
public async Task<ActionResult<string>> Certify(string? gid, string? mac,
[FromQuery(Name = "r")]string? random, [FromQuery(Name = "md")]string? md5)
{
var host = Request.Host.Value;
var command = new CertifyCommand(gid, mac, random, md5, host);
return Ok(await Mediator.Send(command));
}
[HttpGet("data.php")]
public async Task<ActionResult<string>> GetData()
{
var query = new GetDataQuery(Request.Host.Value);
return Ok(await Mediator.Send(query));
}
}

View File

@ -0,0 +1,19 @@
using Application.Game.Option;
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("service/option")]
public class ServiceOptionController : BaseController<ServiceOptionController>
{
[HttpGet("PlayInfo.php")]
public async Task<ActionResult<string>> GetPlayCount([FromQuery(Name = "card_id")] long cardId)
{
var query = new PlayCountQuery(cardId);
var count = await Mediator.Send(query);
return Ok("1\n" +
$"{count}");
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("update/cgi")]
public class UpdateController : ControllerBase
{
// TODO: Check update properly
[HttpGet("check.php")]
public IActionResult UpdateCheck()
{
return NotFound();
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace MainServer.Controllers.Game;
[ApiController]
[Route("service/upload")]
public class UploadController : ControllerBase
{
private const string UPLOAD_RESPONSE = "1\n" +
"OK";
[HttpPost("upload.php")]
public IActionResult Upload()
{
return Ok(UPLOAD_RESPONSE);
}
}

View File

@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>11</LangVersion>
<Version>3.0.0-alpha</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CertificateManager" Version="1.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="6.1.1-dev-00293" />
<PackageReference Include="Serilog.Expressions" Version="3.4.2-dev-00119" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
<PackageReference Include="Throw" Version="1.3.1" />
</ItemGroup>
<ItemGroup>
<Content Remove="Configurations\database.json" />
<None Include="Configurations\database.json" />
<Content Remove="Configurations\events.json" />
<None Include="Configurations\events.json" />
<Content Remove="Configurations\game.json" />
<None Include="Configurations\game.json" />
<Content Remove="Configurations\logging.json" />
<None Include="Configurations\logging.json" />
<Content Remove="Configurations\matching.json" />
<None Include="Configurations\matching.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\API" />
</ItemGroup>
</Project>

103
MainServer/Program.cs Normal file
View File

@ -0,0 +1,103 @@
using System.Reflection;
using Application;
using Application.Interfaces;
using Domain.Config;
using Infrastructure;
using Infrastructure.Common;
using Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Serilog.Extensions.Logging;
using Throw;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateBootstrapLogger();
var version = Assembly.GetEntryAssembly()?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion;
Log.Information("GCLocalServer version {Version}", version);
Log.Information("Server starting up...");
try
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
const string configurationsDirectory = "Configurations";
builder.Configuration
.AddJsonFile($"{configurationsDirectory}/database.json", optional: false, reloadOnChange: false)
.AddJsonFile($"{configurationsDirectory}/game.json", optional: false, reloadOnChange: false)
.AddJsonFile($"{configurationsDirectory}/logging.json", optional: false, reloadOnChange: false)
.AddJsonFile($"{configurationsDirectory}/events.json", optional: true, reloadOnChange: false)
.AddJsonFile($"{configurationsDirectory}/matching.json", optional: true, reloadOnChange: false)
.AddJsonFile($"{configurationsDirectory}/server.json", optional: true, reloadOnChange: false);
builder.Services.Configure<EventConfig>(
builder.Configuration.GetSection(EventConfig.EVENT_SECTION));
builder.Services.Configure<RelayConfig>(
builder.Configuration.GetSection(RelayConfig.RELAY_SECTION));
var serverIp = builder.Configuration["ServerIp"] ?? "127.0.0.1";
var certificateManager = new CertificateService(serverIp, new SerilogLoggerFactory(Log.Logger).CreateLogger(""));
builder.WebHost.ConfigureKestrel(options =>
options.ConfigureHttpsDefaults(adapterOptions =>
adapterOptions.ServerCertificate = certificateManager.InitializeCertificate()
));
builder.Host.UseSerilog((context, configuration) =>
{
configuration.WriteTo.Console().ReadFrom.Configuration(context.Configuration);
});
builder.Services.AddControllers().AddXmlSerializerFormatters();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<CardDbContext>();
db.Database.Migrate();
}
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms, " +
"request host: {RequestHost}";
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
};
});
var eventService = app.Services.GetService<IEventManagerService>();
eventService.ThrowIfNull();
eventService.InitializeEvents();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Unhandled exception");
}
finally
{
Log.Information("Shut down complete");
Log.CloseAndFlush();
}

View File

@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55391",
"sslPort": 44317
}
},
"profiles": {
"MainServer": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7114;http://localhost:5107",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

79
MainServer/app.manifest Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -0,0 +1,3 @@
{
"AllowedHosts": "*"
}