Добавьте файлы проекта.
This commit is contained in:
parent
212a005438
commit
e3c69229ad
|
@ -0,0 +1,30 @@
|
||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
|
@ -0,0 +1,24 @@
|
||||||
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["Yuna.OauthServer/Yuna.OauthServer.csproj", "Yuna.OauthServer/"]
|
||||||
|
RUN dotnet restore "./Yuna.OauthServer/Yuna.OauthServer.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/Yuna.OauthServer"
|
||||||
|
RUN dotnet build "./Yuna.OauthServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./Yuna.OauthServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "Yuna.OauthServer.dll"]
|
|
@ -0,0 +1,19 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Yuna.OauthServer.Endpoints.Auth.DTO;
|
||||||
|
|
||||||
|
namespace Yuna.OauthServer.Endpoints.Auth
|
||||||
|
{
|
||||||
|
public class AuthEndpoints
|
||||||
|
{
|
||||||
|
public void Define(WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapGet("oauth/auth", Authorize)
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResult Authorize([AsParameters] AuthRequest request)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yuna.OauthServer.Endpoints.Auth.DTO
|
||||||
|
{
|
||||||
|
|
||||||
|
public class AuthRequest
|
||||||
|
{
|
||||||
|
public string response_type { get; set; } = null!;
|
||||||
|
public string client_id { get; set; } = null!;
|
||||||
|
public string redirect_uri { get; set; } = null!;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Yuna.OauthServer.Endpoints.Auth.DTO
|
||||||
|
{
|
||||||
|
public class AuthResponse
|
||||||
|
{
|
||||||
|
public string ResponseType { get;} = "code";
|
||||||
|
public string Code { get; set; } = null!;
|
||||||
|
public string RedirectUri { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace Yuna.OauthServer.Model
|
||||||
|
{
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
|
||||||
|
public Client(string clientName, int clientId, string secret, string clientUri, string redirectUri)
|
||||||
|
{
|
||||||
|
ClientName = clientName;
|
||||||
|
ClientId = clientId;
|
||||||
|
ClientSecret = secret;
|
||||||
|
ClientUri = clientUri;
|
||||||
|
RedirectUri = redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ClientName { get; set; }
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
|
//public string ClientType { get; set; }
|
||||||
|
//public List<string> GrantType { get; set; }
|
||||||
|
public bool IsActive { get; set; } = false;
|
||||||
|
//public List<string> AllowedScopes { get; set; }
|
||||||
|
public string? ClientUri { get; set; }
|
||||||
|
public string RedirectUri { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yuna.OauthServer.Model
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
var summaries = new[]
|
||||||
|
{
|
||||||
|
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||||
|
};
|
||||||
|
|
||||||
|
app.MapGet("/weatherforecast", () =>
|
||||||
|
{
|
||||||
|
var forecast = Enumerable.Range(1, 5).Select(index =>
|
||||||
|
new WeatherForecast
|
||||||
|
(
|
||||||
|
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||||
|
Random.Shared.Next(-20, 55),
|
||||||
|
summaries[Random.Shared.Next(summaries.Length)]
|
||||||
|
))
|
||||||
|
.ToArray();
|
||||||
|
return forecast;
|
||||||
|
})
|
||||||
|
.WithName("GetWeatherForecast")
|
||||||
|
.WithOpenApi();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
|
||||||
|
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
|
||||||
|
{
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5026"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:47304",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Yuna.OauthServer.Model;
|
||||||
|
|
||||||
|
namespace Yuna.OauthServer.Storage
|
||||||
|
{
|
||||||
|
public class ClientsList
|
||||||
|
{
|
||||||
|
public List<Client> Clients = new()
|
||||||
|
{
|
||||||
|
new Client("yandex", 0, "5aS3dFgH7jK9lM1nO5pQrT",)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0-preview1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
@Yuna.OauthServer_HostAddress = http://localhost:5026
|
||||||
|
|
||||||
|
GET {{Yuna.OauthServer_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Storage;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Device;
|
||||||
|
|
||||||
|
namespace Yuna.Tests.Repositories
|
||||||
|
{
|
||||||
|
public class DeviceRepositoriesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_Creates_Device()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new DeviceRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Device device = new Device
|
||||||
|
{
|
||||||
|
Name = "Test Device",
|
||||||
|
Description = "Test Description",
|
||||||
|
DeviceUrl = "Test Url"
|
||||||
|
};
|
||||||
|
var result = await repo.Create(device);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(result.Name, device.Name);
|
||||||
|
Assert.Equal(result.Description, device.Description);
|
||||||
|
Assert.Equal(result.DeviceUrl, device.DeviceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_Gets_DeviceWithId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new DeviceRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await repo.GetById(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(1, result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetList_Gets_List()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new DeviceRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await repo.GetList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Storage;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Prop;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Yuna.Tests.Repositories
|
||||||
|
{
|
||||||
|
public class PropRepositoriesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_Creates_Prop()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new PropRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Prop prop = new()
|
||||||
|
{
|
||||||
|
JsonValueName = "test",
|
||||||
|
MeasureName = "test",
|
||||||
|
Name = "test",
|
||||||
|
};
|
||||||
|
var result = await repo.Create(prop);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(result.Name, prop.Name);
|
||||||
|
Assert.Equal(result.MeasureName, prop.MeasureName);
|
||||||
|
Assert.Equal(result.JsonValueName, prop.JsonValueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_Gets_PropWithId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new PropRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await repo.GetById(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(1, result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetByPropName_Gets_PropWithName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new PropRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await repo.GetByPropName("test");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("test", result.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetList_Gets_List()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new PropRepository(_context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await repo.GetList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
|
||||||
|
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Storage;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.User;
|
||||||
|
|
||||||
|
namespace Yuna.Tests.Repositories
|
||||||
|
{
|
||||||
|
public class UserRepositoriesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_Creates_User()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new UserRepository(_context);
|
||||||
|
//act
|
||||||
|
User user = new User("test", "123");
|
||||||
|
var result = await repo.Create(user);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(result.UserName, user.UserName);
|
||||||
|
Assert.Equal(result.HashedPassword, user.HashedPassword);
|
||||||
|
Assert.Equal(result.IsAdmin, user.IsAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_Gets_UserWithId()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new UserRepository(_context);
|
||||||
|
//act
|
||||||
|
var result = await repo.GetById(1);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(1, result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetByUserName_Gets_UserWithName()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new UserRepository(_context);
|
||||||
|
//act
|
||||||
|
var result = await repo.GetByUsername("test");
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("test", result.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetList_Gets_List()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
Settings.Init();
|
||||||
|
var _context = new DapperContext(true);
|
||||||
|
var repo = new UserRepository(_context);
|
||||||
|
//act
|
||||||
|
var result = await repo.GetList();
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Yuna.Website\Yuna.Website.Server\Yuna.Website.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,79 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Yuna.Website.Server.Services.TokenService;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Services.UserService;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.API
|
||||||
|
{
|
||||||
|
public class AuthEndpoints
|
||||||
|
{
|
||||||
|
public void Define(WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapPost("/api/auth/login", Login)
|
||||||
|
.WithTags("auth")
|
||||||
|
.Produces(200)
|
||||||
|
.Produces(401)
|
||||||
|
.Produces(400);
|
||||||
|
|
||||||
|
app.MapPost("/api/user/register", RegisterUser)
|
||||||
|
.WithTags("auth")
|
||||||
|
.Produces(200)
|
||||||
|
.Produces(400);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string RawPassword { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("username")]
|
||||||
|
public string UserName { get; set; } = null!;
|
||||||
|
}
|
||||||
|
public async Task<IResult> Login(HttpContext context, [FromBody] LoginRequest request, IUserService userService, ITokenService tokenService)
|
||||||
|
{
|
||||||
|
var userFromDb = await userService.GetByUsername(request.UserName);
|
||||||
|
if (userFromDb is null) return Results.Unauthorized();
|
||||||
|
|
||||||
|
var hashedPassword = Encrypter.HashPassword(request.RawPassword, request.UserName);
|
||||||
|
if (!hashedPassword.Equals(userFromDb.HashedPassword)) return Results.Unauthorized();
|
||||||
|
await SetAccessToken(context, tokenService, userFromDb);
|
||||||
|
return Results.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static async Task SetAccessToken(HttpContext context, ITokenService tokenService, User userFromDb)
|
||||||
|
{
|
||||||
|
var identity = tokenService.CreateAccessToken(userFromDb);
|
||||||
|
|
||||||
|
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegisterUserRequest
|
||||||
|
{
|
||||||
|
public string username { get; set; } = null!;
|
||||||
|
public string password { get; set; } = null!;
|
||||||
|
public string referalCode { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> RegisterUser([FromBody] RegisterUserRequest dto, IUserService userService)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!dto.referalCode.Equals(Settings.ReferalCode)) return Results.BadRequest();
|
||||||
|
|
||||||
|
var hashedPassword = Encrypter.HashPassword(dto.password, dto.username);
|
||||||
|
|
||||||
|
var userToRegister = new User(dto.username, hashedPassword);
|
||||||
|
|
||||||
|
var result = await userService.Create(userToRegister);
|
||||||
|
if (result is null) return Results.BadRequest();
|
||||||
|
|
||||||
|
return Results.Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Yuna.Website.Server.Services.DeviceSkillService;
|
||||||
|
using Yuna.Website.Server.Services.DeviceService;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.API
|
||||||
|
{
|
||||||
|
public class DeviceEndpoints
|
||||||
|
{
|
||||||
|
public void Define(WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapPost("/api/device", CreateDevice)
|
||||||
|
.WithTags("device");
|
||||||
|
|
||||||
|
app.MapDelete("/api/device/{id:long}", () => { })
|
||||||
|
.WithTags("device");
|
||||||
|
|
||||||
|
app.MapGet("/api/device/{id:long}", () => { })
|
||||||
|
.WithTags("device");
|
||||||
|
|
||||||
|
app.MapGet("/api/device", GetAll)
|
||||||
|
.WithTags("device");
|
||||||
|
|
||||||
|
app.MapPut("/api/device/{deviceId:long}", AddSkillsToDevice)
|
||||||
|
.WithTags("device");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class CreateDeviceResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonPropertyName("deviceUrl")]
|
||||||
|
public string DeviceUrl { get; set; } = null!;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> CreateDevice([FromBody] CreateDeviceResult request, IDeviceService deviceService)
|
||||||
|
{
|
||||||
|
var device = new Device()
|
||||||
|
{
|
||||||
|
Description = request.Description,
|
||||||
|
DeviceUrl = request.DeviceUrl,
|
||||||
|
Name = request.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await deviceService.Create(device);
|
||||||
|
if (result is null) return Results.BadRequest();
|
||||||
|
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> GetAll(IDeviceService deviceService)
|
||||||
|
{
|
||||||
|
var result = await deviceService.GetList();
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> Delete(IDeviceService deviceService, long id)
|
||||||
|
{
|
||||||
|
var result = await deviceService.Delete(id);
|
||||||
|
if (result is null) return Results.NotFound();
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> AddSkillsToDevice([FromBody] long[] skillsIds, long deviceId, IDeviceService deviceService, IPropService skillService)
|
||||||
|
{
|
||||||
|
var skills = await skillService.GetByIds(skillsIds);
|
||||||
|
if (skills is null) return Results.NotFound("not all skills exist");
|
||||||
|
|
||||||
|
var result = await deviceService.AddProps(skills, deviceId);
|
||||||
|
if (result is null) return Results.NotFound("device");
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Services.DeviceSkillService;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.API
|
||||||
|
{
|
||||||
|
public class SkillsEndpoints
|
||||||
|
{
|
||||||
|
|
||||||
|
public void Define(WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapPost("/api/skill", CreateSkill)
|
||||||
|
.Produces(200)
|
||||||
|
.WithTags("skill");
|
||||||
|
|
||||||
|
app.MapDelete("/api/skill/{id:long}", () => { })
|
||||||
|
.Produces(200)
|
||||||
|
.WithTags("skill");
|
||||||
|
|
||||||
|
app.MapGet("/api/skill/{id:long}", () => { })
|
||||||
|
.Produces(200)
|
||||||
|
.WithTags("skill");
|
||||||
|
|
||||||
|
app.MapGet("/api/skill", GetAllSkills)
|
||||||
|
.Produces(200)
|
||||||
|
.WithTags("skill");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class CreateSkillRequest
|
||||||
|
{
|
||||||
|
public string type { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("measureName")]
|
||||||
|
public string? MeasureName { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("jsonValueName")]
|
||||||
|
public string JsonValueName { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public String Name { get; init; } = null!;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> CreateSkill([FromBody] CreateSkillRequest request, IPropService skillService)
|
||||||
|
{
|
||||||
|
Prop prop = new Prop()
|
||||||
|
{
|
||||||
|
JsonValueName = request.JsonValueName,
|
||||||
|
MeasureName = request.MeasureName ?? "",
|
||||||
|
Name = request.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await skillService.Create(prop);
|
||||||
|
if (result is null) return Results.BadRequest();
|
||||||
|
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IResult> GetAllSkills(IPropService skillService)
|
||||||
|
{
|
||||||
|
var result = await skillService.GetList();
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS with-node
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install curl
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash
|
||||||
|
RUN apt-get -y install nodejs
|
||||||
|
|
||||||
|
|
||||||
|
FROM with-node AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["Yuna.Website/Yuna.Website.Server/Yuna.Website.Server.csproj", "Yuna.Website/Yuna.Website.Server/"]
|
||||||
|
COPY ["yuna.website.client/yuna.website.client.esproj", "yuna.website.client/"]
|
||||||
|
RUN dotnet restore "./Yuna.Website/Yuna.Website.Server/Yuna.Website.Server.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/Yuna.Website/Yuna.Website.Server"
|
||||||
|
RUN dotnet build "./Yuna.Website.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./Yuna.Website.Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "Yuna.Website.Server.dll"]
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"AppVariables": {
|
||||||
|
"AccessTokenLifeTimeMinutes": 1,
|
||||||
|
"RefreshTokenLifeTimeDays": 365,
|
||||||
|
"ReferalCode": "#I_@m_g00d_guy_07_07_2024"
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"BarcodeService": "http://localhost:7799/barcode",
|
||||||
|
"Db": "User ID=developer;Password=magazinchik_forever;Server=localhost;Port=5432;Database=Yuna;Pooling=true;IncludeErrorDetail=true;",
|
||||||
|
"UserDb": "User ID=developer;Password=magazinchik_forever;Server=localhost;Port=5432;Database=UserKeeper;Pooling=true;",
|
||||||
|
"RabbitMq": "host=localhost;username=guest;password=guest",
|
||||||
|
"Redis": "localhost:5002"
|
||||||
|
},
|
||||||
|
"ExternalLinks": {
|
||||||
|
"BookInfo": "https://m.books.ru/ajax/search_autocomplete.php",
|
||||||
|
"BookPictures": "https://book24.ru"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Infrastructure
|
||||||
|
{
|
||||||
|
public static class CollectionExtensions
|
||||||
|
{
|
||||||
|
public static bool IsNullOrEmpty<T>(this T? collection) where T : IList
|
||||||
|
{
|
||||||
|
if (collection is null) return true;
|
||||||
|
if (collection.Count == 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
namespace Yuna.Website.Server.Infrastructure
|
||||||
|
{
|
||||||
|
public static class CookieExtensions
|
||||||
|
{
|
||||||
|
public static T LoadFromCookies<T>(this HttpContext context, string key)
|
||||||
|
{
|
||||||
|
if (context.Request.Cookies.TryGetValue(key, out var value))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return System.Text.Json.JsonSerializer.Deserialize<T>(value)
|
||||||
|
?? throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveToCookies<T>(this HttpContext context, string key, T value, TimeSpan? maxAge = null)
|
||||||
|
{
|
||||||
|
var dataStr = System.Text.Json.JsonSerializer.Serialize<T>(value);
|
||||||
|
|
||||||
|
if (context.Request.Cookies.ContainsKey(key)) context.Response.Cookies.Delete(key);
|
||||||
|
|
||||||
|
context.Response.Cookies.Append(key, dataStr, new CookieOptions() { HttpOnly = true, MaxAge = maxAge ?? TimeSpan.FromDays(365) });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Infrastructure
|
||||||
|
{
|
||||||
|
public class Encrypter
|
||||||
|
{
|
||||||
|
private static HMACSHA256 _passwordHasher = new() { Key = [2, 2, 2, 2, 2, 2, 22, 2, 2, 2, 2, 2, 2, 2] };
|
||||||
|
private static HMACSHA256 _tokenHasher = new() { Key = [21, 12, 21, 21, 2, 11, 111, 2, 2, 21, 2, 21, 2, 2] };
|
||||||
|
public static string HashPassword(string password, string username)
|
||||||
|
{
|
||||||
|
|
||||||
|
var loweredUsername = username.ToLower();
|
||||||
|
|
||||||
|
var hashedPassword = Convert.ToBase64String(_passwordHasher.ComputeHash(Encoding.UTF8.GetBytes(password + loweredUsername)))
|
||||||
|
?? throw new ArgumentNullException("didnt manage to generate password");
|
||||||
|
return hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] CreateTokenSalt(long userId, DateTime creationTime)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetBytes(userId.ToString() + creationTime.ToString("dd_mm_yy___hh_ss"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string HashTokenSalt(byte[] bytesStr)
|
||||||
|
{
|
||||||
|
var hashedSalt = _tokenHasher.ComputeHash(bytesStr);
|
||||||
|
return Convert.ToBase64String(hashedSalt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Infrastructure
|
||||||
|
{
|
||||||
|
public class HttpHelper
|
||||||
|
{
|
||||||
|
public static Func<RedirectContext<CookieAuthenticationOptions>, Task> BypassWithStatusCode(HttpStatusCode statusCode)
|
||||||
|
{
|
||||||
|
return context =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = (int)statusCode;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<RedirectContext<CookieAuthenticationOptions>, Task> BypassWithStatusCode(int statusCode)
|
||||||
|
{
|
||||||
|
return context =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Infrastructure
|
||||||
|
{
|
||||||
|
public static class Settings
|
||||||
|
{
|
||||||
|
public static TimeSpan AccessTokenLifeTime { get; private set; }
|
||||||
|
public static TimeSpan RefreshTokenLifeTime { get; private set; }
|
||||||
|
public static string ReferalCode { get; private set; } = null!;
|
||||||
|
public static string DbConnectionStr { get; private set; } = null!;
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
var jsonText = File.ReadAllText("globalSettings.json");
|
||||||
|
using JsonDocument document = JsonDocument.Parse(jsonText);
|
||||||
|
|
||||||
|
var root = document.RootElement;
|
||||||
|
|
||||||
|
|
||||||
|
var connectionStrs = root.GetProperty("ConnectionStrings");
|
||||||
|
LoadConnectionStrs(connectionStrs);
|
||||||
|
|
||||||
|
var externalLinkStrs = root.GetProperty("ExternalLinks");
|
||||||
|
LoadExternalLinks(externalLinkStrs);
|
||||||
|
|
||||||
|
var appVariablesStr = root.GetProperty("AppVariables");
|
||||||
|
LoadAppVariables(appVariablesStr);
|
||||||
|
|
||||||
|
|
||||||
|
string appName = AppDomain.CurrentDomain.FriendlyName;
|
||||||
|
//DistributedCacheExtensions.Init(appName + "_");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void LoadConnectionStrs(JsonElement connectionStrs)
|
||||||
|
{
|
||||||
|
DbConnectionStr = connectionStrs.GetProperty("Db").GetString()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadExternalLinks(JsonElement externalLinksStr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadAppVariables(JsonElement appVariablesStr)
|
||||||
|
{
|
||||||
|
ReferalCode = appVariablesStr.GetProperty("ReferalCode").GetString() ?? throw new Exception("No ref code");
|
||||||
|
AccessTokenLifeTime = TimeSpan.FromSeconds(appVariablesStr.GetProperty("AccessTokenLifeTimeMinutes").GetInt32());
|
||||||
|
RefreshTokenLifeTime = TimeSpan.FromDays(appVariablesStr.GetProperty("RefreshTokenLifeTimeDays").GetInt32());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace Yuna.Website.Server.Model
|
||||||
|
{
|
||||||
|
public class Device
|
||||||
|
{
|
||||||
|
public Device(string name, string description)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
public Device()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
public string Description { get; set; } = null!;
|
||||||
|
public List<Prop> Props { get; set; } = [];
|
||||||
|
public string DeviceUrl { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Yuna.Website.Server.Model
|
||||||
|
{
|
||||||
|
public class Prop
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
public string MeasureName { get; set; } = null!;
|
||||||
|
public string JsonValueName { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace Yuna.Website.Server.Model
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public User(string userName, string hashedPassword)
|
||||||
|
{
|
||||||
|
UserName = userName;
|
||||||
|
HashedPassword = hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
User()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public long Id { get; init; }
|
||||||
|
public string UserName { get; set; } = null!;
|
||||||
|
public string HashedPassword { get; set; } = null!;
|
||||||
|
public bool IsAdmin { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Yuna.Website.Server;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
Starter.RegisterServices(builder);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
Starter.Configure(app);
|
||||||
|
|
||||||
|
Starter.DefineEndpoints(app);
|
||||||
|
|
||||||
|
Starter.ApplyMigrations(app);
|
||||||
|
|
||||||
|
app.Run();
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5227"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:52505",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
|
||||||
|
using Yuna.Website.Server.Storage;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
using Yuna.Website.Server.Services.DeviceSkillService;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Device;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.DeviceService
|
||||||
|
{
|
||||||
|
public class DeviceService : IDeviceService
|
||||||
|
{
|
||||||
|
private readonly IPropService _propService;
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
public DeviceService(IPropService propService, IDeviceRepository repository)
|
||||||
|
{
|
||||||
|
_propService = propService;
|
||||||
|
_deviceRepository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Device?> AddProps(IReadOnlyList<Prop> props, long deviceId)
|
||||||
|
{
|
||||||
|
var device = await _deviceRepository.GetById(deviceId);
|
||||||
|
if (device is null) return null;
|
||||||
|
|
||||||
|
await _deviceRepository.AddProps(props, deviceId);
|
||||||
|
|
||||||
|
device.Props.AddRange(props);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Device?> Create(Device device)
|
||||||
|
{
|
||||||
|
var result = await _deviceRepository.Create(device);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Device?> Delete(long id)
|
||||||
|
{
|
||||||
|
var prop = await _deviceRepository.GetById(id);
|
||||||
|
if (prop is null) return null;
|
||||||
|
|
||||||
|
var result = await _deviceRepository.Delete(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Device?> GetById(long id)
|
||||||
|
{
|
||||||
|
var result = await _deviceRepository.GetById(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public async Task<IReadOnlyList<Device>> GetList()
|
||||||
|
{
|
||||||
|
var result = await _deviceRepository.GetList();
|
||||||
|
return result ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.DeviceService
|
||||||
|
{
|
||||||
|
public interface IDeviceService
|
||||||
|
{
|
||||||
|
public Task<Device?> GetById(long id);
|
||||||
|
public Task<IReadOnlyList<Device>> GetList();
|
||||||
|
public Task<Device?> Create(Device device);
|
||||||
|
//public Task<User?> Update(User user);
|
||||||
|
public Task<Device?> Delete(long id);
|
||||||
|
public Task<Device?> AddProps(IReadOnlyList<Prop> props, long deviceId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.DeviceSkillService
|
||||||
|
{
|
||||||
|
public interface IPropService
|
||||||
|
{
|
||||||
|
public Task<Prop?> GetById(long id);
|
||||||
|
public Task<Prop?> GetByPropName(string value);
|
||||||
|
public Task<IReadOnlyList<Prop>> GetList();
|
||||||
|
public Task<Prop?> Create(Prop value);
|
||||||
|
//public Task<User?> Update(User user);
|
||||||
|
public Task<Prop?> Delete(long id);
|
||||||
|
public Task<IReadOnlyList<Prop>?> GetByIds(long[] ids);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Prop;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.DeviceSkillService
|
||||||
|
{
|
||||||
|
public class PropService : IPropService
|
||||||
|
{
|
||||||
|
private readonly IPropRepository _propRepository;
|
||||||
|
public PropService(IPropRepository propRepository)
|
||||||
|
{
|
||||||
|
_propRepository = propRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Prop?> Create(Prop prop)
|
||||||
|
{
|
||||||
|
var existingProp = await _propRepository.GetByPropName(prop.Name);
|
||||||
|
if (existingProp is not null) return null;
|
||||||
|
|
||||||
|
var result = await _propRepository.Create(prop);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Prop?> Delete(long id)
|
||||||
|
{
|
||||||
|
var prop = await _propRepository.GetById(id);
|
||||||
|
if (prop is null) return null;
|
||||||
|
|
||||||
|
var result = await _propRepository.Delete(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Prop?> GetById(long id)
|
||||||
|
{
|
||||||
|
var result = await _propRepository.GetById(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Prop>?> GetByIds(long[] ids)
|
||||||
|
{
|
||||||
|
if (ids.Count() == 0) return [];
|
||||||
|
|
||||||
|
var props = await _propRepository.GetByIds(ids);
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Prop?> GetByPropName(string name)
|
||||||
|
{
|
||||||
|
var result = await _propRepository.GetByPropName(name);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Prop>> GetList()
|
||||||
|
{
|
||||||
|
var result = await _propRepository.GetList();
|
||||||
|
return result ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.TokenService
|
||||||
|
{
|
||||||
|
public interface ITokenService
|
||||||
|
{
|
||||||
|
ClaimsIdentity CreateAccessToken(User user);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.TokenService
|
||||||
|
{
|
||||||
|
public class TokenService : ITokenService
|
||||||
|
{
|
||||||
|
//private readonly UserKeeperService _userKeeperService;
|
||||||
|
private readonly ILogger<TokenService> _logger;
|
||||||
|
public TokenService(ILogger<TokenService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClaimsIdentity CreateAccessToken(User user)
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>()
|
||||||
|
{
|
||||||
|
new(ClaimTypes.Name, user.UserName),
|
||||||
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new ClaimsIdentity(claims, "Cookies");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.UserService
|
||||||
|
{
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
public Task<User?> GetById(long id);
|
||||||
|
public Task<User?> GetByUsername(string username);
|
||||||
|
public Task<IReadOnlyList<User>> GetList();
|
||||||
|
public Task<User?> Create(User user);
|
||||||
|
public Task<User?> Update(User user);
|
||||||
|
public Task<User?> Delete(long id);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.User;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Services.UserService
|
||||||
|
{
|
||||||
|
public class UserService : IUserService
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
public UserService(IUserRepository userRepository)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> Create(User user)
|
||||||
|
{
|
||||||
|
var existingUser = await _userRepository.GetByUsername(user.UserName);
|
||||||
|
if (existingUser is not null) return null;
|
||||||
|
|
||||||
|
var result = await _userRepository.Create(user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> Delete(long id)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetById(id);
|
||||||
|
if (user is null) return null;
|
||||||
|
|
||||||
|
var result = await _userRepository.Delete(id);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> GetById(long id)
|
||||||
|
{
|
||||||
|
var result = await _userRepository.GetById(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> GetByUsername(string username)
|
||||||
|
{
|
||||||
|
var user = await _userRepository.GetByUsername(username);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<User>> GetList()
|
||||||
|
{
|
||||||
|
var result = await _userRepository.GetList();
|
||||||
|
return result ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> Update(User user)
|
||||||
|
{
|
||||||
|
var userFromDb = await _userRepository.GetById(user.Id);
|
||||||
|
if(userFromDb is null) return null;
|
||||||
|
|
||||||
|
return await _userRepository.Update(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
using Serilog;
|
||||||
|
using Yuna.Website.Server.API;
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
using Yuna.Website.Server.Services.DeviceService;
|
||||||
|
using Yuna.Website.Server.Services.DeviceSkillService;
|
||||||
|
using Yuna.Website.Server.Services.TokenService;
|
||||||
|
using Yuna.Website.Server.Services.UserService;
|
||||||
|
using Yuna.Website.Server.Storage;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Device;
|
||||||
|
using Yuna.Website.Server.Storage.Repositories.Prop;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server
|
||||||
|
{
|
||||||
|
public class Starter
|
||||||
|
{
|
||||||
|
public static void LoadConfigs(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
Settings.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Логгирование через Serilog: логгирует все в консоль
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
private static void DefineLogger(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
var logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.Console()
|
||||||
|
/* .WriteTo.File($"{DateTime.Now.ToString("dd_MM_yyyy_HH_mm_ss_f")}.txt") */
|
||||||
|
//.Filter.ByIncludingOnly(x => x.Level == Serilog.Events.LogEventLevel.Error)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
builder.Logging.AddSerilog(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DefineDb(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<DapperContext>();
|
||||||
|
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||||
|
Console.WriteLine($"Added Db host: {Settings.DbConnectionStr}");
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplyMigrations(WebApplication app)
|
||||||
|
{
|
||||||
|
//using var scope = app.Services.CreateScope();
|
||||||
|
//var services = scope.ServiceProvider;
|
||||||
|
|
||||||
|
//var context = services.GetRequiredService<AppDbContext>();
|
||||||
|
//if (context.Database.GetPendingMigrations().Any())
|
||||||
|
//{
|
||||||
|
// Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||||
|
// Console.WriteLine($"Applying migrations...");
|
||||||
|
// Console.ResetColor();
|
||||||
|
// context.Database.Migrate();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineEndpoints(WebApplication app)
|
||||||
|
{
|
||||||
|
new AuthEndpoints().Define(app);
|
||||||
|
new SkillsEndpoints().Define(app);
|
||||||
|
new DeviceEndpoints().Define(app);
|
||||||
|
//new FacadeEndpoints().Define(app);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||||
|
Console.WriteLine("Endpoints registered");
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineHttpClient(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подключение кеша для журнала состояний
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
public static void DefineCache(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//builder.Services.AddStackExchangeRedisCache(x =>
|
||||||
|
//{
|
||||||
|
// x.Configuration = builder.Configuration.GetConnectionString("Redis")
|
||||||
|
// ?? throw new Exception("no redis str");
|
||||||
|
// x.InstanceName = "api_";
|
||||||
|
//});
|
||||||
|
|
||||||
|
//Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||||
|
//Console.WriteLine($"Added redis host: {builder.Configuration.GetConnectionString("Redis")}");
|
||||||
|
//Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineMessaging(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//builder.Services.RegisterEasyNetQ(Settings.RabbitMQHost, s => s.EnableSystemTextJson());
|
||||||
|
//DefineQueuePublishers(builder);
|
||||||
|
//DefineQueueSubscribers(builder);
|
||||||
|
//DefineRpcClients(builder);
|
||||||
|
//DefineRpcServers(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineQueuePublishers(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//builder.Services.AddSingleton<ITimeEventPublisher, TimeEventPublisher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineQueueSubscribers(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//builder.Services.AddSingleton<ApiRpcConsumer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineRpcServers(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
//builder.Services.AddHostedService<TelegramReplier>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineRpcClients(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineCustomServices(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddScoped<IUserService, UserService>();
|
||||||
|
builder.Services.AddScoped<IPropService, PropService>();
|
||||||
|
builder.Services.AddScoped<ITokenService, TokenService>();
|
||||||
|
builder.Services.AddScoped<IDeviceService, DeviceService>();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IDeviceRepository, DeviceRepository>();
|
||||||
|
builder.Services.AddScoped<IPropRepository, PropRepository>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DefineAuth(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
builder.Services
|
||||||
|
.AddAuthentication()
|
||||||
|
.AddCookie(options =>
|
||||||
|
{
|
||||||
|
options.Cookie = new()
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
IsEssential = true,
|
||||||
|
Name = "access_token",
|
||||||
|
MaxAge = TimeSpan.FromDays(180)
|
||||||
|
};
|
||||||
|
options.SlidingExpiration = false;
|
||||||
|
options.ExpireTimeSpan = Settings.RefreshTokenLifeTime;
|
||||||
|
options.Events = new()
|
||||||
|
{
|
||||||
|
OnRedirectToAccessDenied = HttpHelper.BypassWithStatusCode(403),
|
||||||
|
OnRedirectToLogin = HttpHelper.BypassWithStatusCode(401)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterServices(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
LoadConfigs(builder);
|
||||||
|
|
||||||
|
DefineMessaging(builder);
|
||||||
|
|
||||||
|
DefineCustomServices(builder);
|
||||||
|
|
||||||
|
DefineDb(builder);
|
||||||
|
|
||||||
|
DefineLogger(builder);
|
||||||
|
|
||||||
|
DefineCache(builder);
|
||||||
|
|
||||||
|
DefineHttpClient(builder);
|
||||||
|
|
||||||
|
DefineAuth(builder);
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddCors();
|
||||||
|
|
||||||
|
|
||||||
|
//builder.Services.AddAutoMapper(typeof(ApplicationProfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void Configure(WebApplication app)
|
||||||
|
{
|
||||||
|
app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
|
||||||
|
app.UseDefaultFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
//app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.MapFallbackToFile("/index.html");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
//using Microsoft.EntityFrameworkCore;
|
||||||
|
//using Yuna.Website.Server.Model;
|
||||||
|
//using Yuna.Website.Server.Storage.StorageModel;
|
||||||
|
//using Yuna.Website.Server.Storage.StorageModel.Skill;
|
||||||
|
|
||||||
|
//namespace Yuna.Website.Server.Storage
|
||||||
|
//{
|
||||||
|
// public class AppDbContext : DbContext
|
||||||
|
// {
|
||||||
|
// public AppDbContext()
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||||
|
// //public virtual DbSet<UserInStorage> Users => Set<UserInStorage>();
|
||||||
|
// //public virtual DbSet<SkillInStorage> Skills => Set<SkillInStorage>();
|
||||||
|
// //public virtual DbSet<DeviceInStorage> Devices => Set<DeviceInStorage>();
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Npgsql;
|
||||||
|
using System.Data;
|
||||||
|
using Yuna.Website.Server.Infrastructure;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Storage
|
||||||
|
{
|
||||||
|
public class DapperContext
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public DapperContext(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DapperContext(bool isTest)
|
||||||
|
{
|
||||||
|
_configuration = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDbConnection? _dbConnection;
|
||||||
|
public IDbConnection Connection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_dbConnection is null) _dbConnection = new NpgsqlConnection(Settings.DbConnectionStr);
|
||||||
|
return _dbConnection;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
using Dapper;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.Device
|
||||||
|
{
|
||||||
|
public class DeviceRepository : IDeviceRepository
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public DeviceRepository(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddProps(IReadOnlyList<Model.Prop> props, long deviceId)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"INSERT INTO ""Yuna_Props_In_Devices""
|
||||||
|
(""PropId"", ""DeviceId"")
|
||||||
|
VALUES
|
||||||
|
{string.Join(", ", props.Select(prop => $"({prop.Id}, {deviceId})"))}";
|
||||||
|
|
||||||
|
await _context.Connection.ExecuteAsync(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.Device?> Create(Model.Device device)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"INSERT INTO ""Yuna_Devices""
|
||||||
|
(""Name"", ""Description"", ""DeviceUrl"")
|
||||||
|
VALUES
|
||||||
|
('{device.Name}', '{device.Description}', '{device.DeviceUrl}')
|
||||||
|
RETURNING *";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleAsync<Model.Device?>(query, device);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Model.Device?> Delete(long id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.Device> GetById(long id)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
d.""Id"" as {nameof(Model.Device.Id)},
|
||||||
|
d.""Name"" as {nameof(Model.Device.Name)},
|
||||||
|
d.""Description"" as {nameof(Model.Device.Description)},
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)}
|
||||||
|
FROM ""Yuna_Devices"" d
|
||||||
|
LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId""
|
||||||
|
LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id""
|
||||||
|
WHERE d.""Id"" = {id}
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleAsync<Model.Device>(query);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Model.Device>> GetList()
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
d.""Id"" as {nameof(Model.Device.Id)},
|
||||||
|
d.""Name"" as {nameof(Model.Device.Name)},
|
||||||
|
d.""Description"" as {nameof(Model.Device.Description)},
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)}
|
||||||
|
FROM ""Yuna_Devices"" d
|
||||||
|
LEFT JOIN ""Yuna_Props_In_Devices"" pd ON d.""Id"" = pd.""DeviceId""
|
||||||
|
LEFT JOIN ""Yuna_Props"" p ON pd.""PropId"" = p.""Id""";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QueryAsync<Model.Device>(query);
|
||||||
|
return result.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.Device
|
||||||
|
{
|
||||||
|
public interface IDeviceRepository
|
||||||
|
{
|
||||||
|
public Task<Model.Device> GetById(long id);
|
||||||
|
public Task<IReadOnlyList<Model.Device>> GetList();
|
||||||
|
public Task<Model.Device?> Create(Model.Device device);
|
||||||
|
//public Task<User?> Update(User user);
|
||||||
|
public Task<Model.Device?> Delete(long id);
|
||||||
|
public Task AddProps(IReadOnlyList<Model.Prop> skills, long deviceId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.Prop
|
||||||
|
{
|
||||||
|
public interface IPropRepository
|
||||||
|
{
|
||||||
|
public Task<Model.Prop?> GetById(long id);
|
||||||
|
public Task<Model.Prop?> GetByPropName(string value);
|
||||||
|
public Task<IReadOnlyList<Model.Prop>> GetList();
|
||||||
|
public Task<Model.Prop?> Create(Model.Prop value);
|
||||||
|
//public Task<User?> Update(User user);
|
||||||
|
public Task<Model.Prop?> Delete(long id);
|
||||||
|
public Task<IReadOnlyList<Model.Prop>?> GetByIds(long[] ids);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.Prop
|
||||||
|
{
|
||||||
|
public class PropRepository : IPropRepository
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public PropRepository(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.Prop?> Create(Model.Prop value)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"INSERT INTO ""Yuna_Props""
|
||||||
|
(""Name"", ""MeasureName"", ""JsonValueName"")
|
||||||
|
VALUES
|
||||||
|
(@{nameof(Model.Prop.Name)}, @{nameof(Model.Prop.MeasureName)}, @{nameof(Model.Prop.JsonValueName)})
|
||||||
|
RETURNING *";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleAsync<Model.Prop?>(query, value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Model.Prop?> Delete(long id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.Prop?> GetById(long id)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)}
|
||||||
|
FROM ""Yuna_Props"" p
|
||||||
|
WHERE p.""Id"" = {id}
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleOrDefaultAsync<Model.Prop>(query);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Model.Prop>?> GetByIds(long[] ids)
|
||||||
|
{
|
||||||
|
var idList = string.Join(", ", ids);
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)}
|
||||||
|
FROM ""Yuna_Props"" p
|
||||||
|
WHERE p.""Id"" IN ({idList})";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QueryAsync<Model.Prop>(query);
|
||||||
|
if (result.Count() != ids.Length) return null;
|
||||||
|
return result.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.Prop?> GetByPropName(string value)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)}
|
||||||
|
FROM ""Yuna_Props"" p
|
||||||
|
WHERE LOWER(p.""Name"") = '{value.ToLower()}'
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleOrDefaultAsync<Model.Prop>(query);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Model.Prop>> GetList()
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
p.""Id"" as {nameof(Model.Prop.Id)},
|
||||||
|
p.""Name"" as {nameof(Model.Prop.Name)},
|
||||||
|
p.""MeasureName"" as {nameof(Model.Prop.MeasureName)},
|
||||||
|
p.""JsonValueName"" as {nameof(Model.Prop.JsonValueName)}
|
||||||
|
FROM ""Yuna_Props"" p";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QueryAsync<Model.Prop>(query);
|
||||||
|
return result.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.User
|
||||||
|
{
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
public Task<Model.User?> GetById(long id);
|
||||||
|
public Task<Model.User?> GetByUsername(string username);
|
||||||
|
public Task<IReadOnlyList<Model.User>> GetList();
|
||||||
|
public Task<Model.User?> Create(Model.User? user);
|
||||||
|
public Task<Model.User?> Update(Model.User? user);
|
||||||
|
public Task<Model.User?> Delete(long id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
using Dapper;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Yuna.Website.Server.Model;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Storage.Repositories.User
|
||||||
|
{
|
||||||
|
public class UserRepository : IUserRepository
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public UserRepository(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
public async Task<Model.User?> Create(Model.User? user)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"INSERT INTO ""Yuna_Users""
|
||||||
|
(""Username"", ""HashedPassword"", ""IsAdmin"")
|
||||||
|
VALUES
|
||||||
|
(@{nameof(Model.User.UserName)}, @{nameof(Model.User.HashedPassword)}, @{nameof(Model.User.IsAdmin)})
|
||||||
|
RETURNING *
|
||||||
|
";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleAsync<Model.User?>(query, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Model.User?> Delete(long id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.User?> GetById(long id)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
u.""Id"" as {nameof(Model.User.Id)},
|
||||||
|
u.""Username"" as {nameof(Model.User.UserName)},
|
||||||
|
u.""HashedPassword"" as {nameof(Model.User.HashedPassword)},
|
||||||
|
u.""IsAdmin"" as {nameof(Model.User.IsAdmin)}
|
||||||
|
FROM ""Yuna_Users"" u
|
||||||
|
WHERE u.""Id"" = {id}
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleAsync<Model.User>(query);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.User?> GetByUsername(string username)
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
u.""Id"" as {nameof(Model.User.Id)},
|
||||||
|
u.""Username"" as {nameof(Model.User.UserName)},
|
||||||
|
u.""HashedPassword"" as {nameof(Model.User.HashedPassword)},
|
||||||
|
u.""IsAdmin"" as {nameof(Model.User.IsAdmin)}
|
||||||
|
FROM ""Yuna_Users"" u
|
||||||
|
WHERE LOWER(u.""Username"") = '{username.ToLower()}'
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleOrDefaultAsync<Model.User>(query);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Model.User>> GetList()
|
||||||
|
{
|
||||||
|
var query =
|
||||||
|
$@"SELECT
|
||||||
|
u.""Id"" as {nameof(Model.User.Id)},
|
||||||
|
u.""Username"" as {nameof(Model.User.UserName)},
|
||||||
|
u.""HashedPassword"" as {nameof(Model.User.HashedPassword)},
|
||||||
|
u.""IsAdmin"" as {nameof(Model.User.IsAdmin)}
|
||||||
|
FROM ""Yuna_Users"" u";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QueryAsync<Model.User>(query);
|
||||||
|
return result.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Model.User?> Update(Model.User? user)
|
||||||
|
{
|
||||||
|
var query = $@"
|
||||||
|
UPDATE ""Yuna_Users""
|
||||||
|
SET
|
||||||
|
""Username"" = @{nameof(Model.User.UserName)},
|
||||||
|
""HashedPassword"" = @{nameof(Model.User.HashedPassword)},
|
||||||
|
""IsAdmin"" = {nameof(Model.User.IsAdmin)}
|
||||||
|
""WHERE Id"" = @{nameof(Model.User.Id)}
|
||||||
|
RETURNING *
|
||||||
|
";
|
||||||
|
|
||||||
|
var result = await _context.Connection.QuerySingleOrDefaultAsync<Model.User>(query, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerfileContext>..\..</DockerfileContext>
|
||||||
|
<SpaRoot>..\yuna.website.client</SpaRoot>
|
||||||
|
<SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
|
||||||
|
<SpaProxyServerUrl>https://localhost:5173</SpaProxyServerUrl>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
||||||
|
<Version>8.*-*</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Npgsql" Version="8.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\yuna.website.client\yuna.website.client.esproj">
|
||||||
|
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
@Yuna.Website.Server_HostAddress = http://localhost:5227
|
||||||
|
|
||||||
|
GET {{Yuna.Website.Server_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1,30 @@
|
||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
||||||
|
- Configure the top-level `parserOptions` property like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
// other rules...
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||||
|
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||||
|
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
<disabledPackageSources>
|
||||||
|
<clear />
|
||||||
|
</disabledPackageSources>
|
||||||
|
</configuration>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "yuna.website.client",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
||||||
|
"@typescript-eslint/parser": "^7.13.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^5.3.1",
|
||||||
|
"@types/node": "^20.12.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
#root {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: #F2F2F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(odd) {
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
interface Forecast {
|
||||||
|
date: string;
|
||||||
|
temperatureC: number;
|
||||||
|
temperatureF: number;
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [forecasts, setForecasts] = useState<Forecast[]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
populateWeatherData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const contents = forecasts === undefined
|
||||||
|
? <p><em>Loading... Please refresh once the ASP.NET backend has started. See <a href="https://aka.ms/jspsintegrationreact">https://aka.ms/jspsintegrationreact</a> for more details.</em></p>
|
||||||
|
: <table className="table table-striped" aria-labelledby="tabelLabel">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Temp. (C)</th>
|
||||||
|
<th>Temp. (F)</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{forecasts.map(forecast =>
|
||||||
|
<tr key={forecast.date}>
|
||||||
|
<td>{forecast.date}</td>
|
||||||
|
<td>{forecast.temperatureC}</td>
|
||||||
|
<td>{forecast.temperatureF}</td>
|
||||||
|
<td>{forecast.summary}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 id="tabelLabel">Weather forecast</h1>
|
||||||
|
<p>This component demonstrates fetching data from the server.</p>
|
||||||
|
{contents}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function populateWeatherData() {
|
||||||
|
const response = await fetch('weatherforecast');
|
||||||
|
const data = await response.json();
|
||||||
|
setForecasts(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
After Width: | Height: | Size: 4.0 KiB |
|
@ -0,0 +1,68 @@
|
||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import plugin from '@vitejs/plugin-react';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import child_process from 'child_process';
|
||||||
|
import { env } from 'process';
|
||||||
|
|
||||||
|
const baseFolder =
|
||||||
|
env.APPDATA !== undefined && env.APPDATA !== ''
|
||||||
|
? `${env.APPDATA}/ASP.NET/https`
|
||||||
|
: `${env.HOME}/.aspnet/https`;
|
||||||
|
|
||||||
|
const certificateName = "yuna.website.client";
|
||||||
|
const certFilePath = path.join(baseFolder, `${certificateName}.pem`);
|
||||||
|
const keyFilePath = path.join(baseFolder, `${certificateName}.key`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) {
|
||||||
|
if (0 !== child_process.spawnSync('dotnet', [
|
||||||
|
'dev-certs',
|
||||||
|
'https',
|
||||||
|
'--export-path',
|
||||||
|
certFilePath,
|
||||||
|
'--format',
|
||||||
|
'Pem',
|
||||||
|
'--no-password',
|
||||||
|
], { stdio: 'inherit', }).status) {
|
||||||
|
throw new Error("Could not create certificate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
|
||||||
|
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:5227';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [plugin()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'^/weatherforecast': {
|
||||||
|
target,
|
||||||
|
secure: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port: 5173,
|
||||||
|
https: {
|
||||||
|
key: fs.readFileSync(keyFilePath),
|
||||||
|
cert: fs.readFileSync(certFilePath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.271090-alpha">
|
||||||
|
<PropertyGroup>
|
||||||
|
<StartupCommand>npm run dev</StartupCommand>
|
||||||
|
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
||||||
|
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
|
||||||
|
<!-- Allows the build (or compile) script located on package.json to run on Build -->
|
||||||
|
<ShouldRunBuildScript>false</ShouldRunBuildScript>
|
||||||
|
<!-- Folder where production build objects will be placed -->
|
||||||
|
<BuildOutputFolder>$(MSBuildProjectDirectory)\dist</BuildOutputFolder>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.9.34607.119
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yuna.OauthServer", "Yuna.OauthServer\Yuna.OauthServer.csproj", "{28C99E60-5614-47D9-9640-CFF3F1B1A81F}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yuna.Website.Server", "Yuna.Website\Yuna.Website.Server\Yuna.Website.Server.csproj", "{52C97D16-8D70-4E62-8038-A6137C5268E3}"
|
||||||
|
EndProject
|
||||||
|
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "yuna.website.client", "Yuna.Website\yuna.website.client\yuna.website.client.esproj", "{12C022B1-3125-49DA-9E41-DD2FEAABBB11}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yuna.Tests", "Yuna.Tests\Yuna.Tests.csproj", "{47ECE6AC-C195-4EDF-B40C-AA44DBF8E107}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{52C97D16-8D70-4E62-8038-A6137C5268E3} = {52C97D16-8D70-4E62-8038-A6137C5268E3}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{28C99E60-5614-47D9-9640-CFF3F1B1A81F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{28C99E60-5614-47D9-9640-CFF3F1B1A81F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{28C99E60-5614-47D9-9640-CFF3F1B1A81F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{28C99E60-5614-47D9-9640-CFF3F1B1A81F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{52C97D16-8D70-4E62-8038-A6137C5268E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{52C97D16-8D70-4E62-8038-A6137C5268E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{52C97D16-8D70-4E62-8038-A6137C5268E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{52C97D16-8D70-4E62-8038-A6137C5268E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{12C022B1-3125-49DA-9E41-DD2FEAABBB11}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
|
{47ECE6AC-C195-4EDF-B40C-AA44DBF8E107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{47ECE6AC-C195-4EDF-B40C-AA44DBF8E107}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{47ECE6AC-C195-4EDF-B40C-AA44DBF8E107}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{47ECE6AC-C195-4EDF-B40C-AA44DBF8E107}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {500342BE-FB68-4EF2-9AA3-CBDA72544BF6}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
Loading…
Reference in New Issue