ref: project structure

This commit is contained in:
2026-08-19 23:25:08 +07:00
parent 05a93cfc09
commit a1fe31cf70
155 changed files with 3339 additions and 1007 deletions
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Documents;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class DocumentRepository(AppDbContext db) : RepositoryBase<Document>(db), IDocumentRepository
{
public Task<Document?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(d => d.Id == id, ct);
public Task<List<Document>> GetTreeForProjectAsync(Guid projectId, CancellationToken ct = default) =>
Set.Where(d => d.ProjectId == projectId).OrderBy(d => d.Title).ToListAsync(ct);
public Task<Document?> GetInProjectAsync(Guid parentId, Guid projectId, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(d => d.Id == parentId && d.ProjectId == projectId, ct);
public Task<Guid?> GetParentIdAsync(Guid documentId, CancellationToken ct = default) =>
Set.Where(d => d.Id == documentId).Select(d => d.ParentId).SingleAsync(ct);
public Task<List<Document>> GetChildrenAsync(Guid parentId, CancellationToken ct = default) =>
Set.Where(d => d.ParentId == parentId).ToListAsync(ct);
public Task<List<Document>> SearchAsync(List<Guid> projectIds, string? term, int take, CancellationToken ct = default)
{
var query = Set.Where(d => projectIds.Contains(d.ProjectId));
if (!string.IsNullOrWhiteSpace(term))
{
var lower = term.Trim().ToLower();
query = query.Where(d => d.Title.ToLower().Contains(lower));
}
return query.OrderByDescending(d => d.UpdatedAt).Take(take).ToListAsync(ct);
}
public Task<int> CountByTypeForProjectAsync(Guid projectId, DocumentType type, CancellationToken ct = default) =>
Set.CountAsync(d => d.ProjectId == projectId && d.Type == type, ct);
public Task<List<Document>> GetRecentForProjectAsync(Guid projectId, DocumentType type, int take, CancellationToken ct = default) =>
Set.Where(d => d.ProjectId == projectId && d.Type == type).OrderByDescending(d => d.UpdatedAt).Take(take).ToListAsync(ct);
}
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.MasterData;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class MasterDataRepository(AppDbContext db) : RepositoryBase<MasterDataEntry>(db), IMasterDataRepository
{
public Task<PagedResult<MasterDataEntry>> GetAllAsync(string? group, int page, int pageSize, CancellationToken ct = default) =>
Set.Where(m => group == null || m.Group == group)
.OrderBy(m => m.Group).ThenBy(m => m.SortOrder).ThenBy(m => m.Label)
.ToPagedResultAsync(page, pageSize, ct);
public Task<List<MasterDataEntry>> GetActiveByGroupAsync(string group, CancellationToken ct = default) =>
Set.Where(m => m.Group == group && m.IsActive)
.OrderBy(m => m.SortOrder).ThenBy(m => m.Label)
.ToListAsync(ct);
public Task<MasterDataEntry?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(m => m.Id == id, ct);
public Task<bool> ExistsAsync(string group, string value, Guid? excludeId, CancellationToken ct = default) =>
Set.AnyAsync(m => m.Group == group && m.Value == value && (excludeId == null || m.Id != excludeId), ct);
}
@@ -0,0 +1,92 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Projects;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class ProjectRepository(AppDbContext db) : RepositoryBase<Project>(db), IProjectRepository
{
public Task<Project?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(p => p.Id == id, ct);
public Task<Project?> GetForUserAsync(Guid userId, Guid projectId, CancellationToken ct = default) =>
Set.Include(p => p.CreatedByUser).Include(p => p.UpdatedByUser)
.FirstOrDefaultAsync(p => p.Id == projectId && p.Members.Any(m => m.UserId == userId), ct);
public Task<PagedResult<Project>> GetForUserAsync(Guid userId, int page, int pageSize, CancellationToken ct = default) =>
Set.Include(p => p.CreatedByUser).Include(p => p.UpdatedByUser)
.Where(p => p.Members.Any(m => m.UserId == userId))
.OrderByDescending(p => p.UpdatedAt)
.ToPagedResultAsync(page, pageSize, ct);
public Task<List<Project>> SearchForUserAsync(Guid userId, string? term, CancellationToken ct = default)
{
var query = Set.Include(p => p.CreatedByUser).Include(p => p.UpdatedByUser)
.Where(p => p.Members.Any(m => m.UserId == userId));
if (!string.IsNullOrWhiteSpace(term))
{
var lower = term.Trim().ToLower();
query = query.Where(p => p.Name.ToLower().Contains(lower));
}
return query.OrderByDescending(p => p.UpdatedAt).ToListAsync(ct);
}
public Task<int> CountMembersAsync(Guid projectId, CancellationToken ct = default) =>
Db.ProjectMembers.CountAsync(m => m.ProjectId == projectId, ct);
public Task<bool> IsMemberAsync(Guid projectId, Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers.AnyAsync(m => m.ProjectId == projectId && m.UserId == userId, ct);
public Task<MemberRole?> GetMemberRoleAsync(Guid projectId, Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers
.Where(m => m.ProjectId == projectId && m.UserId == userId)
.Select(m => (MemberRole?)m.Role)
.SingleOrDefaultAsync(ct);
public Task<ProjectMember?> GetMemberAsync(Guid projectId, Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers.FirstOrDefaultAsync(m => m.ProjectId == projectId && m.UserId == userId, ct);
public Task<ProjectMember?> GetMemberWithUserAsync(Guid projectId, Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers.Include(m => m.User).FirstOrDefaultAsync(m => m.ProjectId == projectId && m.UserId == userId, ct);
public Task<PagedResult<ProjectMember>> GetMembersWithUserAsync(Guid projectId, int page, int pageSize, CancellationToken ct = default) =>
Db.ProjectMembers
.Where(m => m.ProjectId == projectId)
.Include(m => m.User)
.OrderByDescending(m => m.Role)
.ThenBy(m => m.User.DisplayName)
.ToPagedResultAsync(page, pageSize, ct);
public Task<int> CountOwnersAsync(Guid projectId, CancellationToken ct = default) =>
Db.ProjectMembers.CountAsync(m => m.ProjectId == projectId && m.Role == MemberRole.Owner, ct);
public Task<List<Guid>> GetProjectIdsForUserAsync(Guid userId, CancellationToken ct = default) =>
Set.Where(p => p.Members.Any(m => m.UserId == userId)).Select(p => p.Id).ToListAsync(ct);
public Task<List<Guid>> GetOwnedProjectIdsAsync(Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers.Where(m => m.UserId == userId && m.Role == MemberRole.Owner).Select(m => m.ProjectId).ToListAsync(ct);
public Task<List<Guid>> GetDocumentViewableProjectIdsAsync(Guid userId, CancellationToken ct = default) =>
Db.ProjectMembers
.Where(m => m.UserId == userId &&
(m.Role == MemberRole.Owner ||
Db.ProjectMemberPermissions.Any(p => p.ProjectId == m.ProjectId && p.UserId == userId && p.Screen == ProjectPermissionScreens.Documents && p.CanView)))
.Select(m => m.ProjectId)
.ToListAsync(ct);
public void AddMember(ProjectMember member) => Db.ProjectMembers.Add(member);
public void RemoveMember(ProjectMember member) => Db.ProjectMembers.Remove(member);
public Task<ProjectMemberPermission?> GetMemberPermissionAsync(Guid projectId, Guid userId, string screen, CancellationToken ct = default) =>
Db.ProjectMemberPermissions.FirstOrDefaultAsync(p => p.ProjectId == projectId && p.UserId == userId && p.Screen == screen, ct);
public Task<Dictionary<Guid, ProjectMemberPermission>> GetMemberPermissionsAsync(Guid projectId, string screen, CancellationToken ct = default) =>
Db.ProjectMemberPermissions
.Where(p => p.ProjectId == projectId && p.Screen == screen)
.ToDictionaryAsync(p => p.UserId, ct);
public void AddMemberPermission(ProjectMemberPermission permission) => Db.ProjectMemberPermissions.Add(permission);
}
@@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common.Repositories;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public abstract class RepositoryBase<T>(AppDbContext db) : IRepository<T> where T : class
{
protected readonly AppDbContext Db = db;
protected DbSet<T> Set => Db.Set<T>();
public void Add(T entity) => Set.Add(entity);
public void Remove(T entity) => Set.Remove(entity);
}
@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Roles;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class RoleRepository(AppDbContext db) : RepositoryBase<Role>(db), IRoleRepository
{
public Task<PagedResult<Role>> GetAllWithPermissionsAsync(int page, int pageSize, CancellationToken ct = default) =>
Set.Include(r => r.Permissions).OrderBy(r => r.Name).ToPagedResultAsync(page, pageSize, ct);
public Task<Role?> GetByIdWithPermissionsAsync(Guid id, CancellationToken ct = default) =>
Set.Include(r => r.Permissions).FirstOrDefaultAsync(r => r.Id == id, ct);
public Task<Role?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(r => r.Id == id, ct);
public Task<bool> ExistsByNameAsync(string name, Guid? excludeId, CancellationToken ct = default) =>
Set.AnyAsync(r => r.Name == name && (excludeId == null || r.Id != excludeId), ct);
public Task<RolePermission?> GetPermissionAsync(Guid roleId, string screen, CancellationToken ct = default) =>
Db.RolePermissions.FirstOrDefaultAsync(p => p.RoleId == roleId && p.Screen == screen, ct);
public Task<Dictionary<string, RolePermission>> GetPermissionsAsync(Guid roleId, CancellationToken ct = default) =>
Db.RolePermissions.Where(p => p.RoleId == roleId).ToDictionaryAsync(p => p.Screen, ct);
public Task<List<RolePermission>> GetPermissionsForRolesAsync(IEnumerable<Guid> roleIds, CancellationToken ct = default) =>
Db.RolePermissions.Where(p => roleIds.Contains(p.RoleId)).ToListAsync(ct);
public void RemovePermissions(IEnumerable<RolePermission> permissions) => Db.RolePermissions.RemoveRange(permissions);
}
@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Tasks;
using TaskPriority = mws.backend.dotnet.domain.Tasks.TaskPriority;
using TaskStatus = mws.backend.dotnet.domain.Tasks.TaskStatus;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class TaskRepository(AppDbContext db) : RepositoryBase<TaskItem>(db), ITaskRepository
{
public Task<TaskItem?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(t => t.Id == id, ct);
public Task<TaskItem?> GetWithAssigneeAsync(Guid id, CancellationToken ct = default) =>
Set.Include(t => t.Assignee).FirstOrDefaultAsync(t => t.Id == id, ct);
public Task<PagedResult<TaskItem>> GetForProjectAsync(
Guid projectId, TaskStatus? status, TaskPriority? priority, Guid? assigneeId, int page, int pageSize, CancellationToken ct = default)
{
var query = Set.Where(t => t.ProjectId == projectId);
if (status.HasValue)
{
query = query.Where(t => t.Status == status);
}
if (priority.HasValue)
{
query = query.Where(t => t.Priority == priority);
}
if (assigneeId.HasValue)
{
query = query.Where(t => t.AssigneeId == assigneeId);
}
return query.Include(t => t.Assignee).OrderByDescending(t => t.UpdatedAt).ToPagedResultAsync(page, pageSize, ct);
}
public Task<List<TaskItem>> SearchWithAssigneeAsync(List<Guid> projectIds, string? term, int take, CancellationToken ct = default)
{
var query = Set.Where(t => projectIds.Contains(t.ProjectId));
if (!string.IsNullOrWhiteSpace(term))
{
var lower = term.Trim().ToLower();
query = query.Where(t => t.Title.ToLower().Contains(lower));
}
return query.Include(t => t.Assignee).OrderByDescending(t => t.UpdatedAt).Take(take).ToListAsync(ct);
}
public async Task<Dictionary<TaskStatus, int>> CountByStatusForProjectAsync(Guid projectId, CancellationToken ct = default)
{
return await Set.Where(t => t.ProjectId == projectId)
.GroupBy(t => t.Status)
.Select(g => new { Status = g.Key, Count = g.Count() })
.ToDictionaryAsync(a => a.Status, a => a.Count, ct);
}
public Task<List<TaskItem>> GetRecentForProjectAsync(Guid projectId, int take, CancellationToken ct = default) =>
Set.Where(t => t.ProjectId == projectId).OrderByDescending(t => t.UpdatedAt).Take(take).ToListAsync(ct);
}
@@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Users;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class UserRepository(AppDbContext db) : RepositoryBase<User>(db), IUserRepository
{
public Task<User?> GetByIdAsync(Guid id, CancellationToken ct = default) =>
Set.FirstOrDefaultAsync(u => u.Id == id, ct);
public Task<User?> GetByIdWithRoleAsync(Guid id, CancellationToken ct = default) =>
Set.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).FirstOrDefaultAsync(u => u.Id == id, ct);
public Task<User?> GetByUsernameWithRoleAsync(string username, CancellationToken ct = default) =>
Set.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).SingleOrDefaultAsync(u => u.Username == username, ct);
public Task<bool> ExistsByUsernameAsync(string username, CancellationToken ct = default) =>
Set.AnyAsync(u => u.Username == username, ct);
public Task<bool> ExistsByRoleIdAsync(Guid roleId, CancellationToken ct = default) =>
Db.UserRoles.AnyAsync(ur => ur.RoleId == roleId, ct);
public Task<List<Guid>> GetRoleIdsAsync(Guid userId, CancellationToken ct = default) =>
Db.UserRoles.Where(ur => ur.UserId == userId).Select(ur => ur.RoleId).ToListAsync(ct);
public Task<List<User>> SearchWithRoleAsync(string? term, int? take, CancellationToken ct = default)
{
var query = Set.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).AsQueryable();
if (!string.IsNullOrWhiteSpace(term))
{
var lower = term.Trim().ToLower();
query = query.Where(u => u.Username.ToLower().Contains(lower) || u.DisplayName.ToLower().Contains(lower));
}
query = query.OrderBy(u => u.DisplayName);
if (take is { } n)
{
query = query.Take(n);
}
return query.ToListAsync(ct);
}
public Task<PagedResult<User>> SearchWithRolePagedAsync(string? term, int page, int pageSize, CancellationToken ct = default)
{
var query = Set.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).AsQueryable();
if (!string.IsNullOrWhiteSpace(term))
{
var lower = term.Trim().ToLower();
query = query.Where(u => u.Username.ToLower().Contains(lower) || u.DisplayName.ToLower().Contains(lower));
}
return query.OrderBy(u => u.DisplayName).ToPagedResultAsync(page, pageSize, ct);
}
}