Add project
This commit is contained in:
30
Api/.dockerignore
Normal file
30
Api/.dockerignore
Normal 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
33
Api/Api.csproj
Normal 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>
|
||||
37
Api/Controllers/SeasonController.cs
Normal file
37
Api/Controllers/SeasonController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
36
Api/Controllers/SpecializationController.cs
Normal file
36
Api/Controllers/SpecializationController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
36
Api/Controllers/SubjectController.cs
Normal file
36
Api/Controllers/SubjectController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Api/Controllers/VersionController.cs
Normal file
32
Api/Controllers/VersionController.cs
Normal 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;
|
||||
}
|
||||
12
Api/Database/Entities/BaseEntity.cs
Normal file
12
Api/Database/Entities/BaseEntity.cs
Normal 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; }
|
||||
}
|
||||
18
Api/Database/Entities/BaseEntityGuid.cs
Normal file
18
Api/Database/Entities/BaseEntityGuid.cs
Normal 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;
|
||||
}
|
||||
22
Api/Database/Entities/Season.cs
Normal file
22
Api/Database/Entities/Season.cs
Normal 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; }
|
||||
}
|
||||
19
Api/Database/Entities/Specialization.cs
Normal file
19
Api/Database/Entities/Specialization.cs
Normal 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; }
|
||||
}
|
||||
24
Api/Database/Entities/Student.cs
Normal file
24
Api/Database/Entities/Student.cs
Normal 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; }
|
||||
}
|
||||
19
Api/Database/Entities/Subject.cs
Normal file
19
Api/Database/Entities/Subject.cs
Normal 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; }
|
||||
}
|
||||
70
Api/Database/Migrations/20260301222003_CreateDb.Designer.cs
generated
Normal file
70
Api/Database/Migrations/20260301222003_CreateDb.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Api/Database/Migrations/20260301222003_CreateDb.cs
Normal file
44
Api/Database/Migrations/20260301222003_CreateDb.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Api/Database/Migrations/20260305195643_SpecsSubjectsAndSeasons.Designer.cs
generated
Normal file
150
Api/Database/Migrations/20260305195643_SpecsSubjectsAndSeasons.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Api/Database/Migrations/SanStudentContextModelSnapshot.cs
Normal file
147
Api/Database/Migrations/SanStudentContextModelSnapshot.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Api/Database/SanStudentContext.cs
Normal file
22
Api/Database/SanStudentContext.cs
Normal 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
21
Api/Dockerfile
Normal 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"]
|
||||
16
Api/Extensions/CacheExtensions.cs
Normal file
16
Api/Extensions/CacheExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
19
Api/Extensions/PagingExtensions.cs
Normal file
19
Api/Extensions/PagingExtensions.cs
Normal 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
52
Api/Mapping/Mapper.cs
Normal 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
61
Api/Program.cs
Normal 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();
|
||||
33
Api/Properties/launchSettings.json
Normal file
33
Api/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
156
Api/Services/Implementation/SeasonService.cs
Normal file
156
Api/Services/Implementation/SeasonService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
146
Api/Services/Implementation/SpecializationService.cs
Normal file
146
Api/Services/Implementation/SpecializationService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
144
Api/Services/Implementation/SubjectService.cs
Normal file
144
Api/Services/Implementation/SubjectService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
11
Api/Services/Interfaces/ISeasonService.cs
Normal file
11
Api/Services/Interfaces/ISeasonService.cs
Normal 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();
|
||||
}
|
||||
11
Api/Services/Interfaces/IServiceHelper.cs
Normal file
11
Api/Services/Interfaces/IServiceHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
Api/Services/Interfaces/ISpecializationService.cs
Normal file
12
Api/Services/Interfaces/ISpecializationService.cs
Normal 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();
|
||||
}
|
||||
12
Api/Services/Interfaces/ISubjectService.cs
Normal file
12
Api/Services/Interfaces/ISubjectService.cs
Normal 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();
|
||||
}
|
||||
12
Api/appsettings.Development.json
Normal file
12
Api/appsettings.Development.json
Normal 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
9
Api/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user