Add project
All checks were successful
SanStudent Multi-Project Deployment / deploy-api (push) Successful in 41s
SanStudent Multi-Project Deployment / deploy-frontadmin (push) Successful in 41s
SanStudent Multi-Project Deployment / deploy-frontstudent (push) Successful in 40s

This commit is contained in:
aherman-san
2026-03-07 11:14:26 +01:00
parent bca807d4c1
commit b8f03bf6d3
191 changed files with 122377 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,82 @@
name: SanStudent Multi-Project Deployment
run-name: ${{ github.actor }} wdraża system SanStudent 🚀
on:
push:
branches:
- main
paths:
- 'Api/**'
- 'FrontAdmin/**'
- 'FrontStudent/**'
- 'Common/**'
- 'sanstudent.sln'
- '.gitea/workflows/**'
jobs:
# ==========================================
# 1. API
# ==========================================
deploy-api:
runs-on: ubuntu-latest
steps:
- name: Checkout kodu
uses: actions/checkout@v3
- name: Build API Image
run: docker build -t sanstudent-api:latest -f Api/Dockerfile .
- name: Deploy API Container
run: |
docker rm -f api-container || true
docker network create san-network || true
docker run -d \
--name api-container \
--network san-network \
-p 8083:8080 \
-e ConnectionStrings__DefaultConnection="${{ secrets.DB_CONNECTION_STRING }}" \
-e ConnectionStrings__RedisCache="${{ secrets.REDIS_CONNECTION_STRING }}" \
--restart always \
sanstudent-api:latest
# ==========================================
# 2. FRONTADMIN (Blazor WebAssembly)
# ==========================================
deploy-frontadmin:
runs-on: ubuntu-latest
steps:
- name: Checkout kodu
uses: actions/checkout@v3
- name: Build FrontAdmin Image
run: docker build -t sanstudent-frontadmin:latest -f FrontAdmin/Dockerfile .
- name: Deploy FrontAdmin Container
run: |
docker rm -f frontadmin-container || true
docker run -d \
--name frontadmin-container \
-p 8081:80 \
--restart always \
sanstudent-frontadmin:latest
# ==========================================
# 3. FRONTSTUDENT (Blazor WebAssembly)
# ==========================================
deploy-frontstudent:
runs-on: ubuntu-latest
steps:
- name: Checkout kodu
uses: actions/checkout@v3
- name: Build FrontStudent Image
run: docker build -t sanstudent-frontstudent:latest -f FrontStudent/Dockerfile .
- name: Deploy FrontStudent Container
run: |
docker rm -f frontstudent-container || true
docker run -d \
--name frontstudent-container \
-p 8082:80 \
--restart always \
sanstudent-frontstudent:latest

30
Api/.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/**

33
Api/Api.csproj Normal file
View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>0ba4ac51-a551-4875-80a9-1d0b702565c2</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<Version>1.0.0.2</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.3" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.12.54" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,37 @@
using Api.Services.Interfaces;
using Common.Dtos.Season;
using Common.Dtos.Student;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace Api.Controllers;
[ApiController]
public class SeasonController(ISeasonService service) : ControllerBase
{
private readonly ISeasonService _service = service;
[HttpPost]
[Route("admin/season/create")]
public async Task<ActionResult> CreateAsync([FromBody] CreateSeasonRequest request)
{
var result = await _service.CreateAsync(request);
return result.Ok ? Ok(result.CreateSeasonResult) : Conflict(result.Error);
}
[HttpGet]
[Route("admin/season")]
public async Task<ActionResult> GetAllAsync()
{
var result = await _service.GetAllAsync();
return result.Length != 0 ? Ok(result) : NoContent();
}
[HttpGet]
[Route("admin/season/paged")]
public async Task<ActionResult> GetAllPagedAsync([FromQuery] GetSeasonsRequest request)
{
var result = await _service.GetAllPagedAsync(request);
return Ok(result);
}
}

View File

@@ -0,0 +1,36 @@
using Api.Services.Interfaces;
using Common.Dtos.Season;
using Common.Dtos.Specialization;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers;
[ApiController]
public class SpecializationController(ISpecializationService service) : ControllerBase
{
private readonly ISpecializationService _service = service;
[HttpPost]
[Route("admin/specialization/create")]
public async Task<ActionResult> CreateAsync([FromBody] CreateSpecializationRequest request)
{
var result = await _service.CreateAsync(request);
return result.Ok ? Ok(result.CreateSpecializationResult) : Conflict(result.Error);
}
[HttpGet]
[Route("admin/specialization")]
public async Task<ActionResult> GetAllAsync()
{
var result = await _service.GetAllAsync();
return result.Length != 0 ? Ok(result) : NoContent();
}
[HttpGet]
[Route("admin/specialization/paged")]
public async Task<ActionResult> GetAllPagedAsync([FromQuery] GetSpecializationsRequest request)
{
var result = await _service.GetAllPagedAsync(request);
return Ok(result);
}
}

View File

@@ -0,0 +1,36 @@
using Api.Services.Interfaces;
using Common.Dtos.Subject;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers
{
[ApiController]
public class SubjectController(ISubjectService service) : ControllerBase
{
private readonly ISubjectService _service = service;
[HttpPost]
[Route("admin/subject/create")]
public async Task<ActionResult> CreateAsync([FromBody] CreateSubjectRequest request)
{
var result = await _service.CreateAsync(request);
return result.Ok ? Ok(result.CreateSubjectResult) : Conflict(result.Error);
}
[HttpGet]
[Route("admin/subject")]
public async Task<ActionResult> GetAllAsync()
{
var result = await _service.GetAllAsync();
return result.Length != 0 ? Ok(result) : NoContent();
}
[HttpGet]
[Route("admin/subject/paged")]
public async Task<ActionResult> GetAllPagedAsync([FromQuery] GetSubjectsRequest request)
{
var result = await _service.GetAllPagedAsync(request);
return Ok(result);
}
}
}

View File

@@ -0,0 +1,32 @@
using Api.Extensions;
using Api.Services.Interfaces;
using Common.Dtos.Student;
using Common.Extensions;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace Api.Controllers;
[ApiController]
public class VersionController() : ControllerBase
{
[HttpGet]
[Route("version")]
public async Task<ActionResult> GetVersion()
{
var version = GetVersionNumber();
return Ok(version);
}
public static string GetVersionNumber()
{
var assembly = Assembly.GetExecutingAssembly();
var version = new VersionResponse { Version = assembly.GetName().Version!.ToString() };
return version.ToJson();
}
}
public class VersionResponse
{
public string Version { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Api.Database.Entities;
public abstract class BaseEntity
{
[Key]
[Column("Id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Api.Database.Entities;
public abstract class BaseEntityGuid
{
[Key]
[Column("Id")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; } = Guid.CreateVersion7();
[Column("CreatedAt")]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[Column("LastModifiedAt")]
public DateTime LastModifiedAt { get; set; } = DateTime.Now;
}

View File

@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
namespace Api.Database.Entities;
[Table("Seasons")]
public class Season : BaseEntity
{
[Column("Name")]
[NotNull]
[MaxLength(20)]
public string? Name { get; set; }
[Column("StartDate")]
[NotNull]
public DateOnly StartDate { get; set; }
[Column("EndDate")]
[NotNull]
public DateOnly EndDate { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
namespace Api.Database.Entities;
[Table("Specializations")]
public class Specialization : BaseEntity
{
[Column("Name")]
[NotNull]
[MaxLength(200)]
public string? Name { get; set; }
[Column("ShortName")]
[NotNull]
[MaxLength(10)]
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
namespace Api.Database.Entities;
[Table("Students")]
public class Student : BaseEntityGuid
{
[Column("FirstName")]
[NotNull]
[MaxLength(50)]
public string? FirstName { get; set; }
[Column("LastName")]
[NotNull]
[MaxLength(50)]
public string? LastName { get; set; }
[Column("AlbumNumber")]
[NotNull]
[MaxLength(6)]
public string? AlbumNumber { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
namespace Api.Database.Entities;
[Table("Subjects")]
public class Subject : BaseEntity
{
[Column("Name")]
[NotNull]
[MaxLength(200)]
public string? Name { get; set; }
[Column("ShortName")]
[NotNull]
[MaxLength(10)]
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,70 @@
// <auto-generated />
using System;
using Api.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Database.Migrations
{
[DbContext(typeof(SanStudentContext))]
[Migration("20260301222003_CreateDb")]
partial class CreateDb
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Database.Entities.Student", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("AlbumNumber")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("nvarchar(6)")
.HasColumnName("AlbumNumber");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("FirstName");
b.Property<DateTime>("LastModifiedAt")
.HasColumnType("datetime2")
.HasColumnName("LastModifiedAt");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("LastName");
b.HasKey("Id");
b.HasIndex("AlbumNumber")
.IsUnique();
b.ToTable("Students");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Api.Database.Migrations
{
/// <inheritdoc />
public partial class CreateDb : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Students",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FirstName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
LastName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
AlbumNumber = table.Column<string>(type: "nvarchar(6)", maxLength: 6, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
LastModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Students", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Students_AlbumNumber",
table: "Students",
column: "AlbumNumber",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Students");
}
}
}

View File

@@ -0,0 +1,150 @@
// <auto-generated />
using System;
using Api.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Database.Migrations
{
[DbContext(typeof(SanStudentContext))]
[Migration("20260305195643_SpecsSubjectsAndSeasons")]
partial class SpecsSubjectsAndSeasons
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Database.Entities.Season", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateOnly>("EndDate")
.HasColumnType("date")
.HasColumnName("EndDate");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)")
.HasColumnName("Name");
b.Property<DateOnly>("StartDate")
.HasColumnType("date")
.HasColumnName("StartDate");
b.HasKey("Id");
b.ToTable("Seasons");
});
modelBuilder.Entity("Api.Database.Entities.Specialization", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)")
.HasColumnName("Name");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)")
.HasColumnName("ShortName");
b.HasKey("Id");
b.ToTable("Specializations");
});
modelBuilder.Entity("Api.Database.Entities.Student", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("AlbumNumber")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("nvarchar(6)")
.HasColumnName("AlbumNumber");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("FirstName");
b.Property<DateTime>("LastModifiedAt")
.HasColumnType("datetime2")
.HasColumnName("LastModifiedAt");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("LastName");
b.HasKey("Id");
b.HasIndex("AlbumNumber")
.IsUnique();
b.ToTable("Students");
});
modelBuilder.Entity("Api.Database.Entities.Subject", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)")
.HasColumnName("Name");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)")
.HasColumnName("ShortName");
b.HasKey("Id");
b.ToTable("Subjects");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Api.Database.Migrations
{
/// <inheritdoc />
public partial class SpecsSubjectsAndSeasons : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Seasons",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
StartDate = table.Column<DateOnly>(type: "date", nullable: false),
EndDate = table.Column<DateOnly>(type: "date", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Seasons", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Specializations",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ShortName = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Specializations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Subjects",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ShortName = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Subjects", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Seasons");
migrationBuilder.DropTable(
name: "Specializations");
migrationBuilder.DropTable(
name: "Subjects");
}
}
}

View File

@@ -0,0 +1,147 @@
// <auto-generated />
using System;
using Api.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Database.Migrations
{
[DbContext(typeof(SanStudentContext))]
partial class SanStudentContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Database.Entities.Season", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateOnly>("EndDate")
.HasColumnType("date")
.HasColumnName("EndDate");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)")
.HasColumnName("Name");
b.Property<DateOnly>("StartDate")
.HasColumnType("date")
.HasColumnName("StartDate");
b.HasKey("Id");
b.ToTable("Seasons");
});
modelBuilder.Entity("Api.Database.Entities.Specialization", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)")
.HasColumnName("Name");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)")
.HasColumnName("ShortName");
b.HasKey("Id");
b.ToTable("Specializations");
});
modelBuilder.Entity("Api.Database.Entities.Student", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("AlbumNumber")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("nvarchar(6)")
.HasColumnName("AlbumNumber");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("FirstName");
b.Property<DateTime>("LastModifiedAt")
.HasColumnType("datetime2")
.HasColumnName("LastModifiedAt");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)")
.HasColumnName("LastName");
b.HasKey("Id");
b.HasIndex("AlbumNumber")
.IsUnique();
b.ToTable("Students");
});
modelBuilder.Entity("Api.Database.Entities.Subject", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("Id");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)")
.HasColumnName("Name");
b.Property<string>("ShortName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)")
.HasColumnName("ShortName");
b.HasKey("Id");
b.ToTable("Subjects");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Api.Database.Entities;
using Microsoft.EntityFrameworkCore;
namespace Api.Database;
public class SanStudentContext : DbContext
{
public SanStudentContext(DbContextOptions<SanStudentContext> options) : base(options) { }
public required virtual DbSet<Specialization> Specializations { get; set; }
public required virtual DbSet<Subject> Subjects { get; set; }
public virtual required DbSet<Season> Seasons { get; set; }
public required virtual DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(entity =>
{
entity.HasIndex(e => e.AlbumNumber).IsUnique();
});
}
}

21
Api/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
COPY ["Api/Api.csproj", "Api/"]
COPY ["Common/Common.csproj", "Common/"]
RUN dotnet restore "Api/Api.csproj"
COPY . .
WORKDIR /src/Api
RUN dotnet publish "Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "Api.dll"]

View File

@@ -0,0 +1,16 @@
using Common.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
namespace Api.Extensions;
public static class CacheExtensions
{
public static async Task SetWithExpirationTimeAsync<T>(this IDistributedCache cache, string key, T value, int minutes = 10)
{
var options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(minutes));
await cache.SetStringAsync(key, value.ToJson(), options);
}
}

View File

@@ -0,0 +1,19 @@
using Common.Dtos.Common;
using Microsoft.EntityFrameworkCore;
namespace Api.Extensions;
public static class PagingExtensions
{
public static async Task<PagedList<T>> ToPagedListAsync<T>(this IQueryable<T> queryable, int pageNumber, int pageSize)
{
var query = queryable;
int totalItemsCount = await queryable.CountAsync();
int totalPagesCount = (totalItemsCount / pageSize) + 1;
var number = Math.Min(pageNumber, totalPagesCount);
int skip = (number - 1) * pageSize;
var items = await query.Skip(skip).Take(pageSize).ToArrayAsync();
return new PagedList<T>(items, pageNumber, pageSize, totalItemsCount, totalPagesCount);
}
}

52
Api/Mapping/Mapper.cs Normal file
View File

@@ -0,0 +1,52 @@
using Api.Database.Entities;
using Common.Dtos.Season;
using Common.Dtos.Specialization;
using Common.Dtos.Student;
using Common.Dtos.Subject;
namespace Api.Mapping;
public static class Mapper
{
public static SpecializationDto ToSpecializationDto(this Specialization specialization)
{
return new SpecializationDto
{
Id = specialization.Id,
Name = specialization.Name,
ShortName = specialization.ShortName
};
}
public static SubjectDto ToSubjectDto(this Subject subject)
{
return new SubjectDto
{
Id = subject.Id,
Name = subject.Name,
ShortName = subject.ShortName
};
}
public static SeasonDto ToSeasonDto(this Season season)
{
return new SeasonDto
{
Id = season.Id,
Name = season.Name,
StartDate = season.StartDate,
EndDate = season.EndDate
};
}
public static StudentBasicDto ToStudentBasicDto(this Student student)
{
return new StudentBasicDto
{
Id = student.Id,
FirstName = student.FirstName,
LastName = student.LastName,
AlbumNumber = student.AlbumNumber
};
}
}

61
Api/Program.cs Normal file
View File

@@ -0,0 +1,61 @@
using Api.Database;
using Api.Services.Implementation;
using Api.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowBlazorFrontends",
policy =>
{
policy.WithOrigins("https://sanstudent.aherman.eu",
"https://sanstudent.eu",
"https://www.sanstudent.eu",
"https://localhost:7001",
"https://localhost:7000"
)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddDbContext<SanStudentContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("RedisCache");
options.InstanceName = "SanStudentApi_";
});
builder.Services.AddScoped<ISpecializationService, SpecializationService>();
builder.Services.AddScoped<ISubjectService, SubjectService>();
builder.Services.AddScoped<ISeasonService, SeasonService>();
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
app.UseCors("AllowBlazorFrontends");
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.Title = "SanStudent API";
options.Theme = ScalarTheme.BluePlanet;
options.DefaultHttpClient = new(ScalarTarget.CSharp, ScalarClient.HttpClient);
options.CustomCss = "";
options.ShowSidebar = true;
});
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,33 @@
{
"profiles": {
"http": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5001"
},
"https": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "scalar/v1",
"applicationUrl": "https://localhost:5000;http://localhost:5001"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}

View File

@@ -0,0 +1,156 @@
using Api.Database;
using Api.Database.Entities;
using Api.Extensions;
using Api.Mapping;
using Api.Services.Interfaces;
using Common.Dtos.Common;
using Common.Dtos.Season;
using Common.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
namespace Api.Services.Implementation;
public class SeasonService(SanStudentContext context, IDistributedCache cache) : ISeasonService, IServiceHelper<CreateSeasonRequest>
{
private readonly SanStudentContext _context = context;
private readonly IDistributedCache _cache = cache;
private List<string> _cacheKeys = [];
public async Task<CreateSeasonResponse> CreateAsync(CreateSeasonRequest request)
{
try
{
var problems = await Validate(request);
if (problems.Errors.Count > 0)
{
return new CreateSeasonResponse
{
Ok = false,
CreateSeasonResult = string.Join(", ", problems.Errors)
};
}
var season = new Season()
{
Name = request.Name!.Trim(),
StartDate = request.StartDate,
EndDate = request.EndDate
};
_context.Seasons.Add(season);
await _context.SaveChangesAsync();
await RemoveCache();
return new CreateSeasonResponse
{
Ok = true,
CreateSeasonResult = $"Zapisano nowy semestr \"{season.Name}\"",
};
}
catch (Exception error)
{
return new CreateSeasonResponse
{
Ok = false,
CreateSeasonResult = $"Błąd podczas próby zapisu semestru: {error.Message}",
};
}
}
public async Task<SeasonDto[]> GetAllAsync()
{
var key = "seasons";
AddKeyToCache(key);
var cachedSeasons = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSeasons))
return cachedSeasons.FromJson<SeasonDto[]>();
var query = _context.Seasons as IQueryable<Season>;
var result = await query
.Where(s => s.EndDate > DateOnly.FromDateTime(DateTime.Now))
.OrderBy(s => s.StartDate)
.ThenBy(s => s.Name)
.Select(s => s.ToSeasonDto()).ToArrayAsync();
await _cache.SetWithExpirationTimeAsync(key, result, 131487);
return result;
}
public async Task<PagedList<SeasonDto>> GetAllPagedAsync(GetSeasonsRequest request)
{
var key = $"seasons_{request.PageNumber}_{request.PageSize}";
if (!string.IsNullOrWhiteSpace(request.Name))
key += $"_{request.Name.ToLower()}";
if (request.HideObsolete.HasValue && request.HideObsolete.Value)
key += $"_{true}";
AddKeyToCache(key);
var cachedSeasons = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSeasons))
return cachedSeasons.FromJson<PagedList<SeasonDto>>();
var query = _context.Seasons as IQueryable<Season>;
if (!string.IsNullOrEmpty(request.Name))
query = query.Where(s => s.Name.ToLower().StartsWith(request.Name.ToLower()));
if (request.HideObsolete.HasValue && request.HideObsolete.Value)
query = query.Where(s => s.EndDate > DateOnly.FromDateTime(DateTime.Now));
var data = query
.OrderBy(s => s.StartDate)
.ThenBy(s => s.Name)
.Select(s => s.ToSeasonDto());
var result = await data.ToPagedListAsync(request.PageNumber, request.PageSize);
await _cache.SetWithExpirationTimeAsync(key, result, 131487);
return result;
}
public async Task<ValidationProblems> Validate(CreateSeasonRequest request)
{
var problems = new ValidationProblems();
if (string.IsNullOrWhiteSpace(request.Name))
problems.Errors.Add("Brak nazwy semestru");
if (problems.Errors.Count > 0)
return problems;
var season = await _context.Seasons
.FirstOrDefaultAsync(s => s.Name.ToLower() == request.Name!.ToLower());
if (season is not null)
problems.Errors.Add("Semestr o podanej nazwie już istnieje");
season = await _context.Seasons
.FirstOrDefaultAsync(s => s.StartDate == request.StartDate);
if (season is not null)
problems.Errors.Add("Semestr o podanej dacie rozpoczęcia już istnieje");
season = await _context.Seasons
.FirstOrDefaultAsync(s => s.EndDate == request.EndDate);
if (season is not null)
problems.Errors.Add("Semestr o podanej dacie zakończenia już istnieje");
return problems;
}
public async Task RemoveCache()
{
foreach (var cacheKey in _cacheKeys)
await _cache.RemoveAsync(cacheKey);
}
public void AddKeyToCache(string key)
{
if (!_cacheKeys.Contains(key))
_cacheKeys.Add(key);
}
}

View File

@@ -0,0 +1,146 @@
using Api.Database;
using Api.Database.Entities;
using Api.Extensions;
using Api.Mapping;
using Api.Services.Interfaces;
using Common.Dtos.Common;
using Common.Dtos.Season;
using Common.Dtos.Specialization;
using Common.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
namespace Api.Services.Implementation;
public class SpecializationService(SanStudentContext context, IDistributedCache cache)
: ISpecializationService, IServiceHelper<CreateSpecializationRequest>
{
private readonly SanStudentContext _context = context;
private readonly IDistributedCache _cache = cache;
private List<string> _cacheKeys = [];
public async Task<CreateSpecializationResponse> CreateAsync(CreateSpecializationRequest request)
{
try
{
var problems = await Validate(request);
if (problems.Errors.Count > 0)
{
return new CreateSpecializationResponse
{
Ok = false,
CreateSpecializationResult = string.Join(", ", problems.Errors)
};
}
var specialization = new Specialization()
{
Name = request.Name!.Trim(),
ShortName = request.ShortName!.Trim()
};
_context.Specializations.Add(specialization);
await _context.SaveChangesAsync();
await RemoveCache();
return new CreateSpecializationResponse
{
Ok = true,
CreateSpecializationResult = $"Zapisano nową specializację \"{specialization.Name}\"",
};
}
catch (Exception error)
{
return new CreateSpecializationResponse
{
Ok = false,
CreateSpecializationResult = $"Błąd podczas próby zapisu specializacji: {error.Message}",
};
}
}
public async Task<SpecializationDto[]> GetAllAsync()
{
var key = "specializations";
AddKeyToCache(key);
var cachedSpecs = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSpecs))
return cachedSpecs.FromJson<SpecializationDto[]>();
var query = _context.Specializations as IQueryable<Specialization>;
var result = await query
.OrderBy(s => s.Name)
.ThenBy(s => s.ShortName)
.Select(s => s.ToSpecializationDto()).ToArrayAsync();
await _cache.SetWithExpirationTimeAsync(key, result, 262487);
return result;
}
public async Task<PagedList<SpecializationDto>> GetAllPagedAsync(GetSpecializationsRequest request)
{
var key = $"specializations_{request.PageNumber}_{request.PageSize}";
if (!string.IsNullOrWhiteSpace(request.Name))
key += $"_{request.Name.ToLower()}";
AddKeyToCache(key);
var cachedSpecs = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSpecs))
return cachedSpecs.FromJson<PagedList<SpecializationDto>>();
var query = _context.Specializations as IQueryable<Specialization>;
if (!string.IsNullOrEmpty(request.Name))
query = query.Where(s => s.Name.ToLower().StartsWith(request.Name.ToLower()));
var data = query
.OrderBy(s => s.Name)
.ThenBy(s => s.ShortName)
.Select(s => s.ToSpecializationDto());
var result = await data.ToPagedListAsync(request.PageNumber, request.PageSize);
await _cache.SetWithExpirationTimeAsync(key, result, 262487);
return result;
}
public async Task<ValidationProblems> Validate(CreateSpecializationRequest request)
{
var problems = new ValidationProblems();
if (string.IsNullOrWhiteSpace(request.Name))
problems.Errors.Add("Brak nazwy specializacji");
if (string.IsNullOrWhiteSpace(request.ShortName))
problems.Errors.Add("Brak nazwy skróconej specializacji");
if (problems.Errors.Count > 0)
return problems;
var specialization = await _context.Specializations
.FirstOrDefaultAsync(s => s.Name.ToLower() == request.Name!.ToLower());
if (specialization is not null)
problems.Errors.Add("Specializacja o podanej nazwie już istnieje");
specialization = await _context.Specializations
.FirstOrDefaultAsync(s => s.ShortName.ToLower() == request.ShortName!.ToLower());
if (specialization is not null)
problems.Errors.Add("Specializacja o podanej nazwie skróconej już istnieje");
return problems;
}
public async Task RemoveCache()
{
foreach (var cacheKey in _cacheKeys)
await _cache.RemoveAsync(cacheKey);
}
public void AddKeyToCache(string key)
{
if (!_cacheKeys.Contains(key))
_cacheKeys.Add(key);
}
}

View File

@@ -0,0 +1,144 @@
using Api.Database;
using Api.Database.Entities;
using Api.Extensions;
using Api.Mapping;
using Api.Services.Interfaces;
using Common.Dtos.Common;
using Common.Dtos.Subject;
using Common.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
namespace Api.Services.Implementation;
public class SubjectService(SanStudentContext context, IDistributedCache cache)
: ISubjectService, IServiceHelper<CreateSubjectRequest>
{
private readonly SanStudentContext _context = context;
private readonly IDistributedCache _cache = cache;
private List<string> _cacheKeys = [];
public async Task<CreateSubjectResponse> CreateAsync(CreateSubjectRequest request)
{
try
{
var problems = await Validate(request);
if (problems.Errors.Count > 0)
{
return new CreateSubjectResponse
{
Ok = false,
CreateSubjectResult = string.Join(", ", problems.Errors)
};
}
var subject = new Subject()
{
Name = request.Name!.Trim(),
ShortName = request.ShortName!.Trim()
};
_context.Subjects.Add(subject);
await _context.SaveChangesAsync();
await RemoveCache();
return new CreateSubjectResponse
{
Ok = true,
CreateSubjectResult = $"Zapisano nowy przedmiot \"{subject.Name}\"",
};
}
catch (Exception error)
{
return new CreateSubjectResponse
{
Ok = false,
CreateSubjectResult = $"Błąd podczas próby zapisu przedmiotu: {error.Message}",
};
}
}
public async Task<SubjectDto[]> GetAllAsync()
{
var key = "subjects";
AddKeyToCache(key);
var cachedSubjects = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSubjects))
return cachedSubjects.FromJson<SubjectDto[]>();
var query = _context.Subjects as IQueryable<Subject>;
var result = await query
.OrderBy(s => s.Name)
.ThenBy(s => s.ShortName)
.Select(s => s.ToSubjectDto()).ToArrayAsync();
await _cache.SetWithExpirationTimeAsync(key, result, 262487);
return result;
}
public async Task<PagedList<SubjectDto>> GetAllPagedAsync(GetSubjectsRequest request)
{
var key = $"subjects_{request.PageNumber}_{request.PageSize}";
if (!string.IsNullOrWhiteSpace(request.Name))
key += $"_{request.Name.ToLower()}";
AddKeyToCache(key);
var cachedSubjects = await _cache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedSubjects))
return cachedSubjects.FromJson<PagedList<SubjectDto>>();
var query = _context.Subjects as IQueryable<Subject>;
if (!string.IsNullOrEmpty(request.Name))
query = query.Where(s => s.Name.ToLower().StartsWith(request.Name.ToLower()));
var data = query
.OrderBy(s => s.Name)
.ThenBy(s => s.ShortName)
.Select(s => s.ToSubjectDto());
var result = await data.ToPagedListAsync(request.PageNumber, request.PageSize);
await _cache.SetWithExpirationTimeAsync(key, result, 262487);
return result;
}
public async Task<ValidationProblems> Validate(CreateSubjectRequest request)
{
var problems = new ValidationProblems();
if (string.IsNullOrWhiteSpace(request.Name))
problems.Errors.Add("Brak nazwy przedmiotu");
if (string.IsNullOrWhiteSpace(request.ShortName))
problems.Errors.Add("Brak nazwy skróconej przedmiotu");
if (problems.Errors.Count > 0)
return problems;
var subject = await _context.Subjects
.FirstOrDefaultAsync(s => s.Name.ToLower() == request.Name!.ToLower());
if (subject is not null)
problems.Errors.Add("Przedmiot o podanej nazwie już istnieje");
subject = await _context.Subjects
.FirstOrDefaultAsync(s => s.ShortName.ToLower() == request.ShortName!.ToLower());
if (subject is not null)
problems.Errors.Add("Przedmiot o podanej nazwie skróconej już istnieje");
return problems;
}
public void AddKeyToCache(string key)
{
if (!_cacheKeys.Contains(key))
_cacheKeys.Add(key);
}
public async Task RemoveCache()
{
foreach (var cacheKey in _cacheKeys)
await _cache.RemoveAsync(cacheKey);
}
}

View File

@@ -0,0 +1,11 @@
using Common.Dtos.Common;
using Common.Dtos.Season;
namespace Api.Services.Interfaces;
public interface ISeasonService
{
Task<CreateSeasonResponse> CreateAsync(CreateSeasonRequest request);
Task<PagedList<SeasonDto>> GetAllPagedAsync(GetSeasonsRequest request);
Task<SeasonDto[]> GetAllAsync();
}

View File

@@ -0,0 +1,11 @@
using Common.Dtos.Common;
namespace Api.Services.Interfaces
{
public interface IServiceHelper<T>
{
Task<ValidationProblems> Validate(T request);
Task RemoveCache();
void AddKeyToCache(string key);
}
}

View File

@@ -0,0 +1,12 @@
using Common.Dtos.Common;
using Common.Dtos.Season;
using Common.Dtos.Specialization;
namespace Api.Services.Interfaces;
public interface ISpecializationService
{
Task<CreateSpecializationResponse> CreateAsync(CreateSpecializationRequest request);
Task<PagedList<SpecializationDto>> GetAllPagedAsync(GetSpecializationsRequest request);
Task<SpecializationDto[]> GetAllAsync();
}

View File

@@ -0,0 +1,12 @@
using Common.Dtos.Common;
using Common.Dtos.Specialization;
using Common.Dtos.Subject;
namespace Api.Services.Interfaces;
public interface ISubjectService
{
Task<CreateSubjectResponse> CreateAsync(CreateSubjectRequest request);
Task<PagedList<SubjectDto>> GetAllPagedAsync(GetSubjectsRequest request);
Task<SubjectDto[]> GetAllAsync();
}

View File

@@ -0,0 +1,12 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=89.167.52.197;Database=SanStudent;User ID=sanstudentuser;Password=@sanStudent2026@;TrustServerCertificate=True;",
"RedisCache": "localhost:6379,password=#PanTadeusz1973#,abortConnect=false"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
Api/appsettings.json Normal file
View File

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

18
Common/Common.csproj Normal file
View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Requests\**" />
<Compile Remove="Responses\**" />
<EmbeddedResource Remove="Requests\**" />
<EmbeddedResource Remove="Responses\**" />
<None Remove="Requests\**" />
<None Remove="Responses\**" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using System.Net;
namespace Common.Dtos.Common;
public abstract class BaseResponse
{
public bool Ok { get; set; }
public string? Error { get; set; }
}

View File

@@ -0,0 +1,22 @@
namespace Common.Dtos.Common;
public class PagedList<T>
{
public PagedList(T[] items, int pageNumber, int pageSize, int totalItemsCount, int numberOfPages)
{
Items = items;
PageNumber = pageNumber;
PageSize = pageSize;
TotalItemsCount = totalItemsCount;
NumberOfPages = numberOfPages;
}
public static PagedList<T> Empty(int pagedNumber, int pageSize)
=> new([], pagedNumber, pageSize, 0, 0);
public T[] Items { get; }
public int PageNumber { get; }
public int PageSize { get; }
public int TotalItemsCount { get; }
public int NumberOfPages { get; }
}

View File

@@ -0,0 +1,7 @@
namespace Common.Dtos.Common;
public abstract class PagedRequest
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace Common.Dtos.Common;
public class ValidationProblems
{
public ValidationProblems()
{
Errors = [];
}
public List<string> Errors { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Common.Dtos.Season;
public class CreateSeasonRequest
{
public string? Name { get; set; }
public DateOnly StartDate { get; set; }
public DateOnly EndDate { get; set; }
}

View File

@@ -0,0 +1,8 @@
using Common.Dtos.Common;
namespace Common.Dtos.Season;
public class CreateSeasonResponse : BaseResponse
{
public string? CreateSeasonResult { get; set; }
}

View File

@@ -0,0 +1,9 @@
using Common.Dtos.Common;
namespace Common.Dtos.Season;
public class GetSeasonsRequest : PagedRequest
{
public string? Name { get; set; }
public bool? HideObsolete { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace Common.Dtos.Season;
public class SeasonDto
{
public int Id { get; set; }
public string? Name { get; set; }
public DateOnly StartDate { get; set; }
public DateOnly EndDate { get; set; }
public bool IsCurrent => StartDate <= DateOnly.FromDateTime(DateTime.Now) && EndDate >= DateOnly.FromDateTime(DateTime.Now);
}

View File

@@ -0,0 +1,7 @@
namespace Common.Dtos.Specialization;
public class CreateSpecializationRequest
{
public string? Name { get; set; }
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,12 @@
using Common.Dtos.Common;
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.Dtos.Specialization
{
public class CreateSpecializationResponse : BaseResponse
{
public string? CreateSpecializationResult { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
using Common.Dtos.Common;
namespace Common.Dtos.Specialization;
public class GetSpecializationsRequest : PagedRequest
{
public string? Name { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace Common.Dtos.Specialization;
public class SpecializationDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Common.Dtos.Student;
public class CreateStudentRequest
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
public required string AlbumNumber { get; set; }
}

View File

@@ -0,0 +1,8 @@
using Common.Dtos.Common;
namespace Common.Dtos.Student;
public class CreateStudentResponse : BaseResponse
{
public string? CreateStudentResult { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace Common.Dtos.Student;
public class StudentBasicDto
{
public Guid Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? AlbumNumber { get; set; }
public string FullNameReverese => $"{LastName} {FirstName} ({AlbumNumber})";
}

View File

@@ -0,0 +1,7 @@
namespace Common.Dtos.Subject;
public class CreateSubjectRequest
{
public string? Name { get; set; }
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,12 @@
using Common.Dtos.Common;
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.Dtos.Subject
{
public class CreateSubjectResponse : BaseResponse
{
public string? CreateSubjectResult { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
using Common.Dtos.Common;
namespace Common.Dtos.Subject;
public class GetSubjectsRequest : PagedRequest
{
public string? Name { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.Dtos.Subject;
public class SubjectDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? ShortName { get; set; }
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json;
namespace Common.Extensions
{
public static class JsonExtensions
{
private static JsonSerializerOptions _jso = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
public static string ToJson<T>(this T source) => JsonSerializer.Serialize(source, _jso);
public static T FromJson<T>(this string source) => JsonSerializer.Deserialize<T>(source, _jso) ?? default!;
}
}

View File

@@ -0,0 +1,20 @@
using Common.Pagination;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Common.Extensions;
public static class StringExtensions
{
public static IDictionary<string, object?> AsDictionary(this object source,
BindingFlags bindingAttr = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance)
{
return source.GetType().GetProperties(bindingAttr).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source, null)
);
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.Pagination;
public static class PaginationExtensions
{
public static string ToPaginationSummary(this PaginationProperties props)
{
var before = (props.PageSize * (props.PageNumber - 1)) + 1;
var after = Math.Min((props.PageSize * props.PageNumber), props.TotalItems);
return props.TotalItems == 0 ? $"{props.Text}: brak" : $"{props.Text}: {before}-{after} z {props.TotalItems}";
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Common.Pagination;
public class PaginationProperties
{
public PaginationProperties(string text, int pageSize)
{
Text = text;
PageNumber = 1;
PageSize = pageSize;
}
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalItems { get; set; }
public int NumberOfPages { get; set; }
public string? Text { get; set; }
}

30
FrontAdmin/.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/**

6
FrontAdmin/App.razor Normal file
View File

@@ -0,0 +1,6 @@
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>

26
FrontAdmin/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
COPY ["FrontAdmin/FrontAdmin.csproj", "FrontAdmin/"]
COPY ["Common/Common.csproj", "Common/"]
RUN dotnet restore -s https://api.nuget.org/v3/index.json "FrontAdmin/FrontAdmin.csproj"
COPY . .
WORKDIR "/src/FrontAdmin"
RUN find . -type d -name "bin" -o -name "obj" | xargs rm -rf
RUN dotnet publish "FrontAdmin.csproj" -c Release -o /app/publish
FROM nginx:alpine AS final
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=build /app/publish/wwwroot .
COPY FrontAdmin/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>

View File

@@ -0,0 +1,77 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@@ -0,0 +1,39 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">FrontAdmin</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="nav flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View File

@@ -0,0 +1,83 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
min-height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
.nav-scrollable {
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@@ -0,0 +1,18 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@@ -0,0 +1,7 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@@ -0,0 +1,5 @@
@page "/not-found"
@layout MainLayout
<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>

View File

@@ -0,0 +1,57 @@
@page "/weather"
@inject HttpClient Http
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

11
FrontAdmin/Program.cs Normal file
View File

@@ -0,0 +1,11 @@
using FrontAdmin;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5159",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7290;http://localhost:5159",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

10
FrontAdmin/_Imports.razor Normal file
View File

@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using FrontAdmin
@using FrontAdmin.Layout

5
FrontAdmin/libman.json Normal file
View File

@@ -0,0 +1,5 @@
{
"version": "3.0",
"defaultProvider": "cdnjs",
"libraries": []
}

24
FrontAdmin/nginx.conf Normal file
View File

@@ -0,0 +1,24 @@
events { }
http {
include mime.types;
types {
application/wasm wasm;
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# To jest najwa¿niejsza czêœæ dla Blazora:
location / {
try_files $uri $uri/ /index.html =404;
}
# Opcjonalnie: cache dla plików statycznych
location ~* \.(jpg|jpeg|png|gif|ico|css|js|wasm|dll)$ {
expires 7d;
}
}
}

View File

@@ -0,0 +1,115 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.loading-progress {
position: absolute;
display: block;
width: 8rem;
height: 8rem;
inset: 20vh 0 auto 0;
margin: 0 auto 0 auto;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
text-align: start;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FrontAdmin</title>
<base href="/" />
<link rel="preload" id="webassembly" />
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="FrontAdmin.styles.css" rel="stylesheet" />
<script type="importmap"></script>
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,597 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,594 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More