Добавьте файлы проекта.

This commit is contained in:
Vasya Ryzhkoff 2024-07-10 20:19:26 +07:00
parent 212a005438
commit e3c69229ad
74 changed files with 5674 additions and 0 deletions

30
.dockerignore Normal file
View File

@ -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/**

View File

@ -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"]

View File

@ -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)
{
}
}
}

View File

@ -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!;
}
}

View File

@ -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!;
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,5 @@
using System.Text.Json.Serialization;
namespace Yuna.OauthServer.Model
{
}

View File

@ -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);
}

View File

@ -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
}
}
}

View File

@ -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",)
}
}
}

View File

@ -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>

View File

@ -0,0 +1,6 @@
@Yuna.OauthServer_HostAddress = http://localhost:5026
GET {{Yuna.OauthServer_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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"]

View File

@ -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"
}
}

View File

@ -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;
}
}
}

View File

@ -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) });
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
};
}
}
}

View File

@ -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());
}
}
}

View File

@ -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!;
}
}

View File

@ -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!;
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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
}
}
}

View File

@ -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 ?? [];
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 ?? [];
}
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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>();
// }
//}

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -0,0 +1,6 @@
@Yuna.Website.Server_HostAddress = http://localhost:5227
GET {{Yuna.Website.Server_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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 },
],
},
}

View File

@ -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?

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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>,
)

View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -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"]
}

View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}

View File

@ -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"]
}

View File

@ -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),
}
}
})

View File

@ -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>

48
Yuna.sln Normal file
View File

@ -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