This commit is contained in:
2026-07-24 13:11:09 +08:00 Unverified
parent 6c3021ea8d
commit 30d793773c
23 changed files with 3479 additions and 79 deletions
+7
View File
@@ -0,0 +1,7 @@
namespace Jiaowu.Api.Contracts;
public sealed record PagedResult<T>(
IReadOnlyCollection<T> Items,
int Total,
int Page,
int PageSize);
@@ -0,0 +1,215 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Controllers;
[ApiController]
[Authorize]
[Route("api/courses")]
public sealed class CoursesController(AppDbContext db) : ControllerBase
{
private const string WriteRoles =
SystemRoles.SuperAdmin + "," +
SystemRoles.AcademicAdmin + "," +
SystemRoles.CollegeAdmin;
[HttpGet]
public async Task<ActionResult<PagedResult<object>>> Get(
int page = 1,
int pageSize = 20,
string? keyword = null,
Guid? collegeId = null,
CourseNature? nature = null,
bool? isEnabled = null,
CancellationToken cancellationToken = default)
{
page = Math.Max(1, page);
pageSize = Math.Clamp(pageSize, 10, 100);
var source = db.Courses.AsNoTracking().AsQueryable();
var scopedCollegeId = GetScopedCollegeId();
if (scopedCollegeId.HasValue)
source = source.Where(x => x.CollegeId == scopedCollegeId.Value);
if (collegeId.HasValue)
source = source.Where(x => x.CollegeId == collegeId.Value);
if (nature.HasValue)
source = source.Where(x => x.Nature == nature.Value);
if (isEnabled.HasValue)
source = source.Where(x => x.IsEnabled == isEnabled.Value);
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword = keyword.Trim();
source = source.Where(x =>
x.Code.Contains(keyword) ||
x.Name.Contains(keyword) ||
(x.EnglishName != null && x.EnglishName.Contains(keyword)));
}
var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderBy(x => x.SortOrder)
.ThenBy(x => x.Code)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.Id,
x.Code,
x.Name,
x.EnglishName,
x.CollegeId,
CollegeName = x.College!.Name,
x.Credits,
x.TotalHours,
x.LectureHours,
x.PracticeHours,
x.Nature,
x.AssessmentMethod,
x.Description,
x.IsEnabled,
x.SortOrder,
x.CreatedAt
})
.ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
}
[HttpPost]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> Create(
CourseRequest request,
CancellationToken cancellationToken)
{
var validation = await ValidateAsync(request, cancellationToken);
if (validation is not null) return validation;
var entity = new Course
{
Code = request.Code.Trim(),
Name = request.Name.Trim(),
EnglishName = Normalize(request.EnglishName),
CollegeId = request.CollegeId,
Credits = request.Credits,
TotalHours = request.TotalHours,
LectureHours = request.LectureHours,
PracticeHours = request.PracticeHours,
Nature = request.Nature,
AssessmentMethod = request.AssessmentMethod,
Description = Normalize(request.Description),
IsEnabled = request.IsEnabled,
SortOrder = request.SortOrder
};
db.Courses.Add(entity);
return await SaveAsync(entity.Id, true, cancellationToken);
}
[HttpPut("{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> Update(
Guid id,
CourseRequest request,
CancellationToken cancellationToken)
{
var entity = await db.Courses.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.CollegeId)) return Forbid();
var validation = await ValidateAsync(request, cancellationToken);
if (validation is not null) return validation;
entity.Code = request.Code.Trim();
entity.Name = request.Name.Trim();
entity.EnglishName = Normalize(request.EnglishName);
entity.CollegeId = request.CollegeId;
entity.Credits = request.Credits;
entity.TotalHours = request.TotalHours;
entity.LectureHours = request.LectureHours;
entity.PracticeHours = request.PracticeHours;
entity.Nature = request.Nature;
entity.AssessmentMethod = request.AssessmentMethod;
entity.Description = Normalize(request.Description);
entity.IsEnabled = request.IsEnabled;
entity.SortOrder = request.SortOrder;
return await SaveAsync(entity.Id, false, cancellationToken);
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
{
var entity = await db.Courses.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.CollegeId)) return Forbid();
db.Courses.Remove(entity);
return await SaveAsync(id, false, cancellationToken);
}
private async Task<ActionResult?> ValidateAsync(
CourseRequest request,
CancellationToken cancellationToken)
{
if (!CanAccessCollege(request.CollegeId)) return Forbid();
if (!await db.Colleges.AnyAsync(x => x.Id == request.CollegeId, cancellationToken))
return ValidationProblem("所选学院不存在。");
if (request.LectureHours + request.PracticeHours > request.TotalHours)
return ValidationProblem("讲授学时与实践学时之和不能超过总学时。");
return null;
}
private Guid? GetScopedCollegeId()
{
if (!User.IsInRole(SystemRoles.CollegeAdmin))
return null;
return Guid.TryParse(User.FindFirstValue("college_id"), out var collegeId)
? collegeId
: Guid.Empty;
}
private bool CanAccessCollege(Guid collegeId) =>
!GetScopedCollegeId().HasValue || GetScopedCollegeId() == collegeId;
private async Task<ActionResult> SaveAsync(
Guid id,
bool created,
CancellationToken cancellationToken)
{
try
{
await db.SaveChangesAsync(cancellationToken);
return created ? Created(string.Empty, new { id }) : NoContent();
}
catch (DbUpdateException)
{
return Conflict(new ProblemDetails
{
Title = "无法完成操作",
Detail = "课程编码已存在,或课程已被其他教学业务引用。",
Status = StatusCodes.Status409Conflict
});
}
}
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
public sealed record CourseRequest(
[Required, MaxLength(40)] string Code,
[Required, MaxLength(100)] string Name,
[MaxLength(150)] string? EnglishName,
Guid CollegeId,
[Range(typeof(decimal), "0.1", "99")] decimal Credits,
[Range(1, 1000)] int TotalHours,
[Range(0, 1000)] int LectureHours,
[Range(0, 1000)] int PracticeHours,
CourseNature Nature,
AssessmentMethod AssessmentMethod,
[MaxLength(1000)] string? Description,
bool IsEnabled,
int SortOrder);
@@ -29,6 +29,9 @@ public sealed class DashboardController(AppDbContext db) : ControllerBase
Majors = await db.Majors.CountAsync(cancellationToken),
Classes = await db.AdministrativeClasses.CountAsync(cancellationToken),
Classrooms = await db.Classrooms.CountAsync(cancellationToken),
Teachers = await db.Teachers.CountAsync(cancellationToken),
Students = await db.Students.CountAsync(cancellationToken),
Courses = await db.Courses.CountAsync(cancellationToken),
Users = await db.Users.CountAsync(cancellationToken)
}
};
@@ -0,0 +1,410 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Controllers;
[ApiController]
[Authorize(Roles = ReadRoles)]
[Route("api/personnel")]
public sealed class PersonnelController(AppDbContext db) : ControllerBase
{
private const string ReadRoles =
SystemRoles.SuperAdmin + "," +
SystemRoles.AcademicAdmin + "," +
SystemRoles.CollegeAdmin + "," +
SystemRoles.Counselor;
private const string WriteRoles =
SystemRoles.SuperAdmin + "," +
SystemRoles.AcademicAdmin + "," +
SystemRoles.CollegeAdmin;
[HttpGet("teachers")]
public async Task<ActionResult<PagedResult<object>>> GetTeachers(
[FromQuery] PersonnelQuery query,
CancellationToken cancellationToken)
{
var page = NormalizePage(query.Page);
var pageSize = NormalizePageSize(query.PageSize);
var source = db.Teachers.AsNoTracking().AsQueryable();
var scopedCollegeId = GetScopedCollegeId();
if (scopedCollegeId.HasValue)
source = source.Where(x => x.CollegeId == scopedCollegeId.Value);
if (query.CollegeId.HasValue)
source = source.Where(x => x.CollegeId == query.CollegeId);
if (!string.IsNullOrWhiteSpace(query.Keyword))
{
var keyword = query.Keyword.Trim();
source = source.Where(x =>
x.TeacherNumber.Contains(keyword) ||
x.Name.Contains(keyword) ||
(x.Title != null && x.Title.Contains(keyword)));
}
if (query.TeacherStatus.HasValue)
source = source.Where(x => x.Status == query.TeacherStatus);
var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderBy(x => x.TeacherNumber)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.Id,
x.TeacherNumber,
x.Name,
x.Gender,
x.CollegeId,
CollegeName = x.College!.Name,
x.Title,
x.Status,
x.HireDate,
x.IsExternal,
x.Phone,
x.Email,
x.Notes,
x.UserId,
x.CreatedAt
})
.ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
}
[HttpPost("teachers")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> CreateTeacher(
TeacherRequest request,
CancellationToken cancellationToken)
{
var validation = await ValidateCollegeAsync(request.CollegeId, cancellationToken);
if (validation is not null) return validation;
var entity = new Teacher
{
TeacherNumber = request.TeacherNumber.Trim(),
Name = request.Name.Trim(),
Gender = request.Gender,
CollegeId = request.CollegeId,
Title = Normalize(request.Title),
Status = request.Status,
HireDate = request.HireDate,
IsExternal = request.IsExternal,
Phone = Normalize(request.Phone),
Email = Normalize(request.Email),
Notes = Normalize(request.Notes)
};
db.Teachers.Add(entity);
return await SaveCreatedAsync(entity.Id, cancellationToken);
}
[HttpPut("teachers/{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> UpdateTeacher(
Guid id,
TeacherRequest request,
CancellationToken cancellationToken)
{
var entity = await db.Teachers.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.CollegeId)) return Forbid();
var validation = await ValidateCollegeAsync(request.CollegeId, cancellationToken);
if (validation is not null) return validation;
entity.TeacherNumber = request.TeacherNumber.Trim();
entity.Name = request.Name.Trim();
entity.Gender = request.Gender;
entity.CollegeId = request.CollegeId;
entity.Title = Normalize(request.Title);
entity.Status = request.Status;
entity.HireDate = request.HireDate;
entity.IsExternal = request.IsExternal;
entity.Phone = Normalize(request.Phone);
entity.Email = Normalize(request.Email);
entity.Notes = Normalize(request.Notes);
return await SaveNoContentAsync(cancellationToken);
}
[HttpDelete("teachers/{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<IActionResult> DeleteTeacher(Guid id, CancellationToken cancellationToken)
{
var entity = await db.Teachers.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.CollegeId)) return Forbid();
if (entity.UserId.HasValue)
{
return ConflictProblem("教师已关联登录账号,请先解除账号关联后再删除。");
}
db.Teachers.Remove(entity);
return await SaveNoContentAsync(cancellationToken);
}
[HttpGet("students")]
public async Task<ActionResult<PagedResult<object>>> GetStudents(
[FromQuery] PersonnelQuery query,
CancellationToken cancellationToken)
{
var page = NormalizePage(query.Page);
var pageSize = NormalizePageSize(query.PageSize);
var source = db.Students.AsNoTracking().AsQueryable();
var scopedCollegeId = GetScopedCollegeId();
if (scopedCollegeId.HasValue)
{
source = source.Where(x =>
x.AdministrativeClass!.Major!.CollegeId == scopedCollegeId.Value);
}
if (query.CollegeId.HasValue)
{
source = source.Where(x =>
x.AdministrativeClass!.Major!.CollegeId == query.CollegeId.Value);
}
if (query.MajorId.HasValue)
source = source.Where(x => x.AdministrativeClass!.MajorId == query.MajorId);
if (query.ClassId.HasValue)
source = source.Where(x => x.AdministrativeClassId == query.ClassId);
if (query.EnrollmentYear.HasValue)
source = source.Where(x => x.EnrollmentYear == query.EnrollmentYear);
if (query.StudentStatus.HasValue)
source = source.Where(x => x.Status == query.StudentStatus);
if (!string.IsNullOrWhiteSpace(query.Keyword))
{
var keyword = query.Keyword.Trim();
source = source.Where(x =>
x.StudentNumber.Contains(keyword) || x.Name.Contains(keyword));
}
var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderBy(x => x.StudentNumber)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.Id,
x.StudentNumber,
x.Name,
x.Gender,
x.AdministrativeClassId,
ClassName = x.AdministrativeClass!.Name,
MajorId = x.AdministrativeClass.MajorId,
MajorName = x.AdministrativeClass.Major!.Name,
CollegeId = x.AdministrativeClass.Major.CollegeId,
CollegeName = x.AdministrativeClass.Major.College!.Name,
x.EnrollmentYear,
x.EnrollmentDate,
x.Status,
x.DateOfBirth,
x.Phone,
x.Email,
x.Notes,
x.UserId,
x.CreatedAt
})
.ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
}
[HttpPost("students")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> CreateStudent(
StudentRequest request,
CancellationToken cancellationToken)
{
var targetClass = await db.AdministrativeClasses.AsNoTracking()
.Where(x => x.Id == request.AdministrativeClassId)
.Select(x => new { x.Id, CollegeId = x.Major!.CollegeId })
.FirstOrDefaultAsync(cancellationToken);
if (targetClass is null)
return ValidationProblem("所选行政班不存在。");
if (!CanAccessCollege(targetClass.CollegeId))
return Forbid();
var entity = new Student
{
StudentNumber = request.StudentNumber.Trim(),
Name = request.Name.Trim(),
Gender = request.Gender,
AdministrativeClassId = request.AdministrativeClassId,
EnrollmentYear = request.EnrollmentYear,
EnrollmentDate = request.EnrollmentDate,
Status = request.Status,
DateOfBirth = request.DateOfBirth,
Phone = Normalize(request.Phone),
Email = Normalize(request.Email),
Notes = Normalize(request.Notes)
};
db.Students.Add(entity);
return await SaveCreatedAsync(entity.Id, cancellationToken);
}
[HttpPut("students/{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> UpdateStudent(
Guid id,
StudentRequest request,
CancellationToken cancellationToken)
{
var entity = await db.Students
.Include(x => x.AdministrativeClass)
.ThenInclude(x => x!.Major)
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId)) return Forbid();
var targetClass = await db.AdministrativeClasses.AsNoTracking()
.Where(x => x.Id == request.AdministrativeClassId)
.Select(x => new { x.Id, CollegeId = x.Major!.CollegeId })
.FirstOrDefaultAsync(cancellationToken);
if (targetClass is null)
return ValidationProblem("所选行政班不存在。");
if (!CanAccessCollege(targetClass.CollegeId))
return Forbid();
entity.StudentNumber = request.StudentNumber.Trim();
entity.Name = request.Name.Trim();
entity.Gender = request.Gender;
entity.AdministrativeClassId = request.AdministrativeClassId;
entity.EnrollmentYear = request.EnrollmentYear;
entity.EnrollmentDate = request.EnrollmentDate;
entity.Status = request.Status;
entity.DateOfBirth = request.DateOfBirth;
entity.Phone = Normalize(request.Phone);
entity.Email = Normalize(request.Email);
entity.Notes = Normalize(request.Notes);
return await SaveNoContentAsync(cancellationToken);
}
[HttpDelete("students/{id:guid}")]
[Authorize(Roles = WriteRoles)]
public async Task<IActionResult> DeleteStudent(Guid id, CancellationToken cancellationToken)
{
var entity = await db.Students
.Include(x => x.AdministrativeClass)
.ThenInclude(x => x!.Major)
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
if (entity is null) return NotFound();
if (!CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId)) return Forbid();
if (entity.UserId.HasValue)
{
return ConflictProblem("学生已关联登录账号,请先解除账号关联后再删除。");
}
db.Students.Remove(entity);
return await SaveNoContentAsync(cancellationToken);
}
private Guid? GetScopedCollegeId()
{
if (!User.IsInRole(SystemRoles.CollegeAdmin) &&
!User.IsInRole(SystemRoles.Counselor))
{
return null;
}
return Guid.TryParse(User.FindFirstValue("college_id"), out var collegeId)
? collegeId
: Guid.Empty;
}
private bool CanAccessCollege(Guid collegeId) =>
!GetScopedCollegeId().HasValue || GetScopedCollegeId() == collegeId;
private async Task<ActionResult?> ValidateCollegeAsync(
Guid collegeId,
CancellationToken cancellationToken)
{
if (!CanAccessCollege(collegeId)) return Forbid();
if (!await db.Colleges.AnyAsync(x => x.Id == collegeId, cancellationToken))
return ValidationProblem("所选学院不存在。");
return null;
}
private async Task<ActionResult> SaveCreatedAsync(
Guid id,
CancellationToken cancellationToken)
{
try
{
await db.SaveChangesAsync(cancellationToken);
return Created(string.Empty, new { id });
}
catch (DbUpdateException)
{
return ConflictProblem("编号已存在,或关联数据无效。");
}
}
private async Task<ActionResult> SaveNoContentAsync(CancellationToken cancellationToken)
{
try
{
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
catch (DbUpdateException)
{
return ConflictProblem("编号已存在,或该档案已被其他业务引用。");
}
}
private ActionResult ConflictProblem(string detail) =>
Conflict(new ProblemDetails
{
Title = "无法完成操作",
Detail = detail,
Status = StatusCodes.Status409Conflict
});
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static int NormalizePage(int page) => Math.Max(1, page);
private static int NormalizePageSize(int pageSize) => Math.Clamp(pageSize, 10, 100);
}
public sealed record PersonnelQuery(
int Page = 1,
int PageSize = 20,
string? Keyword = null,
Guid? CollegeId = null,
Guid? MajorId = null,
Guid? ClassId = null,
int? EnrollmentYear = null,
TeacherStatus? TeacherStatus = null,
StudentStatus? StudentStatus = null);
public sealed record TeacherRequest(
[Required, MaxLength(30)] string TeacherNumber,
[Required, MaxLength(50)] string Name,
Gender Gender,
Guid CollegeId,
[MaxLength(30)] string? Title,
TeacherStatus Status,
DateOnly? HireDate,
bool IsExternal,
[MaxLength(30)] string? Phone,
[EmailAddress, MaxLength(100)] string? Email,
[MaxLength(500)] string? Notes);
public sealed record StudentRequest(
[Required, MaxLength(30)] string StudentNumber,
[Required, MaxLength(50)] string Name,
Gender Gender,
Guid AdministrativeClassId,
[Range(2000, 2200)] int EnrollmentYear,
DateOnly EnrollmentDate,
StudentStatus Status,
DateOnly? DateOfBirth,
[MaxLength(30)] string? Phone,
[EmailAddress, MaxLength(100)] string? Email,
[MaxLength(500)] string? Notes);
@@ -0,0 +1,89 @@
using Jiaowu.Api.Domain.Common;
namespace Jiaowu.Api.Domain.Academic;
public sealed class Teacher : EntityBase
{
public required string TeacherNumber { get; set; }
public required string Name { get; set; }
public Gender Gender { get; set; }
public Guid CollegeId { get; set; }
public College? College { get; set; }
public string? Title { get; set; }
public TeacherStatus Status { get; set; } = TeacherStatus.Active;
public DateOnly? HireDate { get; set; }
public bool IsExternal { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Notes { get; set; }
public Guid? UserId { get; set; }
}
public sealed class Student : EntityBase
{
public required string StudentNumber { get; set; }
public required string Name { get; set; }
public Gender Gender { get; set; }
public Guid AdministrativeClassId { get; set; }
public AdministrativeClass? AdministrativeClass { get; set; }
public int EnrollmentYear { get; set; }
public DateOnly EnrollmentDate { get; set; }
public StudentStatus Status { get; set; } = StudentStatus.Active;
public DateOnly? DateOfBirth { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Notes { get; set; }
public Guid? UserId { get; set; }
}
public sealed class Course : CatalogEntity
{
public Guid CollegeId { get; set; }
public College? College { get; set; }
public string? EnglishName { get; set; }
public decimal Credits { get; set; }
public int TotalHours { get; set; }
public int LectureHours { get; set; }
public int PracticeHours { get; set; }
public CourseNature Nature { get; set; }
public AssessmentMethod AssessmentMethod { get; set; }
public string? Description { get; set; }
}
public enum Gender
{
Unknown = 0,
Male = 1,
Female = 2
}
public enum TeacherStatus
{
Active = 1,
OnLeave = 2,
Retired = 3,
Departed = 4
}
public enum StudentStatus
{
Active = 1,
Suspended = 2,
Graduated = 3,
Withdrawn = 4
}
public enum CourseNature
{
GeneralRequired = 1,
MajorRequired = 2,
MajorElective = 3,
GeneralElective = 4,
Practice = 5
}
public enum AssessmentMethod
{
Examination = 1,
Assessment = 2
}
@@ -17,6 +17,9 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
public DbSet<Building> Buildings => Set<Building>();
public DbSet<Classroom> Classrooms => Set<Classroom>();
public DbSet<AcademicTerm> AcademicTerms => Set<AcademicTerm>();
public DbSet<Teacher> Teachers => Set<Teacher>();
public DbSet<Student> Students => Set<Student>();
public DbSet<Course> Courses => Set<Course>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void OnModelCreating(ModelBuilder builder)
@@ -42,6 +45,7 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
ConfigureCatalog<Building>(builder);
ConfigureCatalog<Classroom>(builder);
ConfigureCatalog<AcademicTerm>(builder);
ConfigureCatalog<Course>(builder);
builder.Entity<College>()
.HasOne(x => x.Campus)
@@ -76,6 +80,58 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
builder.Entity<AcademicTerm>()
.HasIndex(x => x.IsCurrent);
builder.Entity<Teacher>(entity =>
{
entity.Property(x => x.TeacherNumber).HasMaxLength(30);
entity.Property(x => x.Name).HasMaxLength(50);
entity.Property(x => x.Title).HasMaxLength(30);
entity.Property(x => x.Phone).HasMaxLength(30);
entity.Property(x => x.Email).HasMaxLength(100);
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => x.TeacherNumber).IsUnique();
entity.HasIndex(x => new { x.CollegeId, x.Status });
entity.HasOne(x => x.College)
.WithMany()
.HasForeignKey(x => x.CollegeId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
builder.Entity<Student>(entity =>
{
entity.Property(x => x.StudentNumber).HasMaxLength(30);
entity.Property(x => x.Name).HasMaxLength(50);
entity.Property(x => x.Phone).HasMaxLength(30);
entity.Property(x => x.Email).HasMaxLength(100);
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => x.StudentNumber).IsUnique();
entity.HasIndex(x => new { x.AdministrativeClassId, x.Status });
entity.HasIndex(x => x.EnrollmentYear);
entity.HasOne(x => x.AdministrativeClass)
.WithMany()
.HasForeignKey(x => x.AdministrativeClassId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
builder.Entity<Course>(entity =>
{
entity.Property(x => x.EnglishName).HasMaxLength(150);
entity.Property(x => x.Credits).HasPrecision(5, 2);
entity.Property(x => x.Description).HasMaxLength(1000);
entity.HasIndex(x => new { x.CollegeId, x.Nature });
entity.HasOne(x => x.College)
.WithMany()
.HasForeignKey(x => x.CollegeId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<AuditLog>(entity =>
{
entity.Property(x => x.Method).HasMaxLength(10);
@@ -9,6 +9,7 @@ public sealed class DatabaseInitializer(
AppDbContext db,
RoleManager<ApplicationRole> roleManager,
UserManager<ApplicationUser> userManager,
DevelopmentSqliteMigrator sqliteMigrator,
IConfiguration configuration,
IHostEnvironment environment,
ILogger<DatabaseInitializer> logger)
@@ -18,6 +19,7 @@ public sealed class DatabaseInitializer(
if (environment.IsDevelopment())
{
await db.Database.EnsureCreatedAsync();
await sqliteMigrator.MigrateAsync();
}
else
{
@@ -108,71 +110,182 @@ public sealed class DatabaseInitializer(
private async Task SeedDevelopmentDataAsync()
{
if (await db.Campuses.AnyAsync())
if (!await db.Campuses.AnyAsync())
{
return;
var campus = new Campus
{
Code = "MAIN",
Name = "主校区",
Address = "大学路 1 号"
};
var college = new College
{
Code = "CS",
Name = "计算机学院",
ShortName = "计算机学院",
CampusId = campus.Id
};
var major = new Major
{
Code = "080901",
Name = "计算机科学与技术",
CollegeId = college.Id,
DegreeType = "工学学士",
SchoolingYears = 4
};
var building = new Building
{
Code = "J1",
Name = "第一教学楼",
CampusId = campus.Id
};
db.AddRange(
campus,
college,
major,
new AdministrativeClass
{
Code = "CS2026-01",
Name = "计科 2026-1 班",
MajorId = major.Id,
Grade = 2026,
CounselorName = "陈老师"
},
building,
new Classroom
{
Code = "J1-201",
Name = "J1-201",
BuildingId = building.Id,
Capacity = 60,
RoomType = "多媒体教室",
Equipment = "投影、扩声、录播"
},
new AcademicTerm
{
Code = "2026-2027-1",
Name = "2026—2027 学年第一学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 7),
EndDate = new DateOnly(2027, 1, 17),
IsCurrent = true
});
await db.SaveChangesAsync();
}
var campus = new Campus
{
Code = "MAIN",
Name = "主校区",
Address = "大学路 1 号"
};
var college = new College
{
Code = "CS",
Name = "计算机学院",
ShortName = "计算机学院",
CampusId = campus.Id
};
var major = new Major
{
Code = "080901",
Name = "计算机科学与技术",
CollegeId = college.Id,
DegreeType = "工学学士",
SchoolingYears = 4
};
var building = new Building
{
Code = "J1",
Name = "第一教学楼",
CampusId = campus.Id
};
var computerCollege = await db.Colleges.SingleAsync(x => x.Code == "CS");
var computerClass = await db.AdministrativeClasses
.SingleAsync(x => x.Code == "CS2026-01");
db.AddRange(
campus,
college,
major,
new AdministrativeClass
{
Code = "CS2026-01",
Name = "计科 2026-1 班",
MajorId = major.Id,
Grade = 2026,
CounselorName = "陈老师"
},
building,
new Classroom
{
Code = "J1-201",
Name = "J1-201",
BuildingId = building.Id,
Capacity = 60,
RoomType = "多媒体教室",
Equipment = "投影、扩声、录播"
},
new AcademicTerm
{
Code = "2026-2027-1",
Name = "2026—2027 学年第一学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 7),
EndDate = new DateOnly(2027, 1, 17),
IsCurrent = true
});
if (!await db.Teachers.AnyAsync())
{
db.Teachers.AddRange(
new Teacher
{
TeacherNumber = "T2026001",
Name = "陈明远",
Gender = Gender.Male,
CollegeId = computerCollege.Id,
Title = "副教授",
Status = TeacherStatus.Active,
HireDate = new DateOnly(2018, 7, 1),
Email = "chenmy@example.edu.cn"
},
new Teacher
{
TeacherNumber = "T2026002",
Name = "林书雅",
Gender = Gender.Female,
CollegeId = computerCollege.Id,
Title = "讲师",
Status = TeacherStatus.Active,
HireDate = new DateOnly(2022, 9, 1),
Email = "linsy@example.edu.cn"
});
}
if (!await db.Students.AnyAsync())
{
db.Students.AddRange(
new Student
{
StudentNumber = "202601001",
Name = "周启航",
Gender = Gender.Male,
AdministrativeClassId = computerClass.Id,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 7),
Status = StudentStatus.Active
},
new Student
{
StudentNumber = "202601002",
Name = "许知夏",
Gender = Gender.Female,
AdministrativeClassId = computerClass.Id,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 7),
Status = StudentStatus.Active
},
new Student
{
StudentNumber = "202601003",
Name = "方嘉树",
Gender = Gender.Male,
AdministrativeClassId = computerClass.Id,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 7),
Status = StudentStatus.Active
});
}
if (!await db.Courses.AnyAsync())
{
db.Courses.AddRange(
new Course
{
Code = "CS101",
Name = "程序设计基础",
EnglishName = "Fundamentals of Programming",
CollegeId = computerCollege.Id,
Credits = 4,
TotalHours = 64,
LectureHours = 40,
PracticeHours = 24,
Nature = CourseNature.MajorRequired,
AssessmentMethod = AssessmentMethod.Examination,
Description = "面向一年级学生的程序设计入门课程。"
},
new Course
{
Code = "CS201",
Name = "数据结构",
EnglishName = "Data Structures",
CollegeId = computerCollege.Id,
Credits = 3.5m,
TotalHours = 56,
LectureHours = 40,
PracticeHours = 16,
Nature = CourseNature.MajorRequired,
AssessmentMethod = AssessmentMethod.Examination
},
new Course
{
Code = "CS305",
Name = "软件工程实践",
EnglishName = "Software Engineering Practice",
CollegeId = computerCollege.Id,
Credits = 2,
TotalHours = 48,
LectureHours = 8,
PracticeHours = 40,
Nature = CourseNature.Practice,
AssessmentMethod = AssessmentMethod.Assessment
});
}
await db.SaveChangesAsync();
}
@@ -0,0 +1,169 @@
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Infrastructure.Persistence;
public sealed class DevelopmentSqliteMigrator(
AppDbContext db,
ILogger<DevelopmentSqliteMigrator> logger)
{
private const string PeopleAndCoursesMigration = "20260724_01_people_courses";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
if (!db.Database.IsSqlite())
{
return;
}
await db.Database.ExecuteSqlRawAsync(
"""
CREATE TABLE IF NOT EXISTS "__DevelopmentSchemaHistory" (
"MigrationId" TEXT NOT NULL CONSTRAINT "PK___DevelopmentSchemaHistory" PRIMARY KEY,
"AppliedAt" TEXT NOT NULL
);
""",
cancellationToken);
var applied = await db.Database
.SqlQuery<string>(
$"""
SELECT "MigrationId" AS "Value"
FROM "__DevelopmentSchemaHistory"
WHERE "MigrationId" = {PeopleAndCoursesMigration}
""")
.AnyAsync(cancellationToken);
if (applied)
{
return;
}
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
foreach (var statement in PeopleAndCoursesStatements)
{
await db.Database.ExecuteSqlRawAsync(statement, cancellationToken);
}
await db.Database.ExecuteSqlInterpolatedAsync(
$"""
INSERT INTO "__DevelopmentSchemaHistory" ("MigrationId", "AppliedAt")
VALUES ({PeopleAndCoursesMigration}, {DateTime.UtcNow});
""",
cancellationToken);
await transaction.CommitAsync(cancellationToken);
logger.LogInformation(
"Applied SQLite development schema migration {MigrationId}.",
PeopleAndCoursesMigration);
}
private static readonly string[] PeopleAndCoursesStatements =
[
"""
CREATE TABLE IF NOT EXISTS "Teachers" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Teachers" PRIMARY KEY,
"TeacherNumber" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"Gender" INTEGER NOT NULL,
"CollegeId" TEXT NOT NULL,
"Title" TEXT NULL,
"Status" INTEGER NOT NULL,
"HireDate" TEXT NULL,
"IsExternal" INTEGER NOT NULL,
"Phone" TEXT NULL,
"Email" TEXT NULL,
"Notes" TEXT NULL,
"UserId" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Teachers_Colleges_CollegeId"
FOREIGN KEY ("CollegeId") REFERENCES "Colleges" ("Id") ON DELETE RESTRICT,
CONSTRAINT "FK_Teachers_AspNetUsers_UserId"
FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE SET NULL
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_Teachers_TeacherNumber"
ON "Teachers" ("TeacherNumber");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Teachers_CollegeId_Status"
ON "Teachers" ("CollegeId", "Status");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Teachers_UserId"
ON "Teachers" ("UserId");
""",
"""
CREATE TABLE IF NOT EXISTS "Students" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Students" PRIMARY KEY,
"StudentNumber" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"Gender" INTEGER NOT NULL,
"AdministrativeClassId" TEXT NOT NULL,
"EnrollmentYear" INTEGER NOT NULL,
"EnrollmentDate" TEXT NOT NULL,
"Status" INTEGER NOT NULL,
"DateOfBirth" TEXT NULL,
"Phone" TEXT NULL,
"Email" TEXT NULL,
"Notes" TEXT NULL,
"UserId" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_Students_AdministrativeClasses_AdministrativeClassId"
FOREIGN KEY ("AdministrativeClassId") REFERENCES "AdministrativeClasses" ("Id")
ON DELETE RESTRICT,
CONSTRAINT "FK_Students_AspNetUsers_UserId"
FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE SET NULL
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_Students_StudentNumber"
ON "Students" ("StudentNumber");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Students_AdministrativeClassId_Status"
ON "Students" ("AdministrativeClassId", "Status");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Students_EnrollmentYear"
ON "Students" ("EnrollmentYear");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Students_UserId"
ON "Students" ("UserId");
""",
"""
CREATE TABLE IF NOT EXISTS "Courses" (
"Id" TEXT NOT NULL CONSTRAINT "PK_Courses" PRIMARY KEY,
"CollegeId" TEXT NOT NULL,
"EnglishName" TEXT NULL,
"Credits" TEXT NOT NULL,
"TotalHours" INTEGER NOT NULL,
"LectureHours" INTEGER NOT NULL,
"PracticeHours" INTEGER NOT NULL,
"Nature" INTEGER NOT NULL,
"AssessmentMethod" INTEGER NOT NULL,
"Description" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
"Code" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"SortOrder" INTEGER NOT NULL,
"IsEnabled" INTEGER NOT NULL,
CONSTRAINT "FK_Courses_Colleges_CollegeId"
FOREIGN KEY ("CollegeId") REFERENCES "Colleges" ("Id") ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_Courses_Code" ON "Courses" ("Code");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Courses_IsEnabled_SortOrder"
ON "Courses" ("IsEnabled", "SortOrder");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Courses_CollegeId_Nature"
ON "Courses" ("CollegeId", "Nature");
"""
];
}
@@ -0,0 +1,984 @@
// <auto-generated />
using System;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
[DbContext(typeof(AppDbContext))]
[Migration("20260724045307_PeopleAndCourses")]
partial class PeopleAndCourses
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AcademicTerm", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("AcademicYear")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateOnly>("EndDate")
.HasColumnType("date");
b.Property<bool>("IsCurrent")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Season")
.HasColumnType("int");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateOnly>("StartDate")
.HasColumnType("date");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("IsCurrent");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("AcademicTerms");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<string>("CounselorName")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("Grade")
.HasColumnType("int");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<Guid>("MajorId")
.HasColumnType("char(36)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("MajorId");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("AdministrativeClasses");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("CampusId")
.HasColumnType("char(36)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("CampusId");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Buildings");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Campus", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Address")
.HasColumnType("longtext");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Campuses");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("BuildingId")
.HasColumnType("char(36)");
b.Property<int>("Capacity")
.HasColumnType("int");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Equipment")
.HasColumnType("longtext");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("RoomType")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("BuildingId");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Classrooms");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid?>("CampusId")
.HasColumnType("char(36)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("ShortName")
.HasColumnType("longtext");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("CampusId");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Colleges");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("AssessmentMethod")
.HasColumnType("int");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<Guid>("CollegeId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("Credits")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<string>("EnglishName")
.HasMaxLength(150)
.HasColumnType("varchar(150)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("LectureHours")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Nature")
.HasColumnType("int");
b.Property<int>("PracticeHours")
.HasColumnType("int");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<int>("TotalHours")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("CollegeId", "Nature");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Courses");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<Guid>("CollegeId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DegreeType")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("SchoolingYears")
.HasColumnType("int");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("CollegeId");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Majors");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("AdministrativeClassId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateOnly?>("DateOfBirth")
.HasColumnType("date");
b.Property<string>("Email")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateOnly>("EnrollmentDate")
.HasColumnType("date");
b.Property<int>("EnrollmentYear")
.HasColumnType("int");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("StudentNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("EnrollmentYear");
b.HasIndex("StudentNumber")
.IsUnique();
b.HasIndex("UserId");
b.HasIndex("AdministrativeClassId", "Status");
b.ToTable("Students");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("CollegeId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<DateOnly?>("HireDate")
.HasColumnType("date");
b.Property<bool>("IsExternal")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TeacherNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<string>("Title")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TeacherNumber")
.IsUnique();
b.HasIndex("UserId");
b.HasIndex("CollegeId", "Status");
b.ToTable("Teachers");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<int>("DataScope")
.HasColumnType("int");
b.Property<string>("Description")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<Guid?>("CollegeId")
.HasColumnType("char(36)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime(6)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<string>("StaffNumber")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.HasIndex("StaffNumber");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.System.AuditLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("IpAddress")
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("varchar(300)");
b.Property<int>("StatusCode")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UserId")
.HasColumnType("char(36)");
b.Property<string>("UserName")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.ToTable("AuditLogs");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<Guid>("RoleId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.Property<Guid>("RoleId")
.HasColumnType("char(36)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.Major", "Major")
.WithMany()
.HasForeignKey("MajorId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Major");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "Campus")
.WithMany()
.HasForeignKey("CampusId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Campus");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.Building", "Building")
.WithMany()
.HasForeignKey("BuildingId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Building");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "Campus")
.WithMany()
.HasForeignKey("CampusId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Campus");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
.WithMany()
.HasForeignKey("CollegeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("College");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
.WithMany()
.HasForeignKey("CollegeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("College");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass")
.WithMany()
.HasForeignKey("AdministrativeClassId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("AdministrativeClass");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
.WithMany()
.HasForeignKey("CollegeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("College");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,190 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class PeopleAndCourses : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Courses",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
CollegeId = table.Column<Guid>(type: "char(36)", nullable: false),
EnglishName = table.Column<string>(type: "varchar(150)", maxLength: 150, nullable: true),
Credits = table.Column<decimal>(type: "decimal(5,2)", precision: 5, scale: 2, nullable: false),
TotalHours = table.Column<int>(type: "int", nullable: false),
LectureHours = table.Column<int>(type: "int", nullable: false),
PracticeHours = table.Column<int>(type: "int", nullable: false),
Nature = table.Column<int>(type: "int", nullable: false),
AssessmentMethod = table.Column<int>(type: "int", nullable: false),
Description = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
Code = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false),
Name = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false),
SortOrder = table.Column<int>(type: "int", nullable: false),
IsEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Courses", x => x.Id);
table.ForeignKey(
name: "FK_Courses_Colleges_CollegeId",
column: x => x.CollegeId,
principalTable: "Colleges",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Students",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
StudentNumber = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false),
Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false),
Gender = table.Column<int>(type: "int", nullable: false),
AdministrativeClassId = table.Column<Guid>(type: "char(36)", nullable: false),
EnrollmentYear = table.Column<int>(type: "int", nullable: false),
EnrollmentDate = table.Column<DateOnly>(type: "date", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
DateOfBirth = table.Column<DateOnly>(type: "date", nullable: true),
Phone = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: true),
Email = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true),
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
UserId = table.Column<Guid>(type: "char(36)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Students", x => x.Id);
table.ForeignKey(
name: "FK_Students_AdministrativeClasses_AdministrativeClassId",
column: x => x.AdministrativeClassId,
principalTable: "AdministrativeClasses",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Students_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "Teachers",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
TeacherNumber = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false),
Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false),
Gender = table.Column<int>(type: "int", nullable: false),
CollegeId = table.Column<Guid>(type: "char(36)", nullable: false),
Title = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
HireDate = table.Column<DateOnly>(type: "date", nullable: true),
IsExternal = table.Column<bool>(type: "tinyint(1)", nullable: false),
Phone = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: true),
Email = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true),
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
UserId = table.Column<Guid>(type: "char(36)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Teachers", x => x.Id);
table.ForeignKey(
name: "FK_Teachers_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_Teachers_Colleges_CollegeId",
column: x => x.CollegeId,
principalTable: "Colleges",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Courses_Code",
table: "Courses",
column: "Code",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Courses_CollegeId_Nature",
table: "Courses",
columns: new[] { "CollegeId", "Nature" });
migrationBuilder.CreateIndex(
name: "IX_Courses_IsEnabled_SortOrder",
table: "Courses",
columns: new[] { "IsEnabled", "SortOrder" });
migrationBuilder.CreateIndex(
name: "IX_Students_AdministrativeClassId_Status",
table: "Students",
columns: new[] { "AdministrativeClassId", "Status" });
migrationBuilder.CreateIndex(
name: "IX_Students_EnrollmentYear",
table: "Students",
column: "EnrollmentYear");
migrationBuilder.CreateIndex(
name: "IX_Students_StudentNumber",
table: "Students",
column: "StudentNumber",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Students_UserId",
table: "Students",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Teachers_CollegeId_Status",
table: "Teachers",
columns: new[] { "CollegeId", "Status" });
migrationBuilder.CreateIndex(
name: "IX_Teachers_TeacherNumber",
table: "Teachers",
column: "TeacherNumber",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Teachers_UserId",
table: "Teachers",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Courses");
migrationBuilder.DropTable(
name: "Students");
migrationBuilder.DropTable(
name: "Teachers");
}
}
}
@@ -72,7 +72,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("AcademicTerms");
b.ToTable("AcademicTerms", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b =>
@@ -121,7 +121,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("AdministrativeClasses");
b.ToTable("AdministrativeClasses", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b =>
@@ -164,7 +164,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Buildings");
b.ToTable("Buildings", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Campus", b =>
@@ -205,7 +205,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Campuses");
b.ToTable("Campuses", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b =>
@@ -258,7 +258,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Classrooms");
b.ToTable("Classrooms", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b =>
@@ -304,7 +304,77 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Colleges");
b.ToTable("Colleges", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("AssessmentMethod")
.HasColumnType("int");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<Guid>("CollegeId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("Credits")
.HasPrecision(5, 2)
.HasColumnType("decimal(5,2)");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<string>("EnglishName")
.HasMaxLength(150)
.HasColumnType("varchar(150)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("LectureHours")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Nature")
.HasColumnType("int");
b.Property<int>("PracticeHours")
.HasColumnType("int");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<int>("TotalHours")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.HasIndex("CollegeId", "Nature");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Courses", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
@@ -354,7 +424,144 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("Majors");
b.ToTable("Majors", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("AdministrativeClassId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateOnly?>("DateOfBirth")
.HasColumnType("date");
b.Property<string>("Email")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateOnly>("EnrollmentDate")
.HasColumnType("date");
b.Property<int>("EnrollmentYear")
.HasColumnType("int");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("StudentNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("EnrollmentYear");
b.HasIndex("StudentNumber")
.IsUnique();
b.HasIndex("UserId");
b.HasIndex("AdministrativeClassId", "Status");
b.ToTable("Students", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("CollegeId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<DateOnly?>("HireDate")
.HasColumnType("date");
b.Property<bool>("IsExternal")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("Notes")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TeacherNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<string>("Title")
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TeacherNumber")
.IsUnique();
b.HasIndex("UserId");
b.HasIndex("CollegeId", "Status");
b.ToTable("Teachers", (string)null);
});
modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationRole", b =>
@@ -519,7 +726,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("CreatedAt");
b.ToTable("AuditLogs");
b.ToTable("AuditLogs", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
@@ -664,6 +871,17 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Navigation("Campus");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
.WithMany()
.HasForeignKey("CollegeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("College");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
@@ -675,6 +893,38 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Navigation("College");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass")
.WithMany()
.HasForeignKey("AdministrativeClassId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("AdministrativeClass");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
.WithMany()
.HasForeignKey("CollegeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("College");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null)
+1
View File
@@ -83,6 +83,7 @@ builder.Services.Configure<JwtOptions>(
builder.Configuration.GetSection(JwtOptions.SectionName));
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<DatabaseInitializer>();
builder.Services.AddScoped<DevelopmentSqliteMigrator>();
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)