diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7ee9c56..a2d23e5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ *.rider.project.model.nuget.info *.sln.iml rider.module.iml +*.log *.cache *.info diff --git a/Data/ApplicationDbContextFactory.cs b/Data/ApplicationDbContextFactory.cs deleted file mode 100644 index 5ebe8e9..0000000 --- a/Data/ApplicationDbContextFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Data.DataContext; -using System.IO; - -namespace Data; - -public class ApplicationDbContextFactory : IDesignTimeDbContextFactory -{ - public ApplicationDbContext CreateDbContext(string[] args) - { - var solutionDir = Directory.GetParent(AppContext.BaseDirectory) - ?.Parent?.Parent?.Parent?.Parent?.FullName - ?? Directory.GetCurrentDirectory(); - - var dataProjectDir = Path.Combine(solutionDir, "Data"); - var dbPath = Path.Combine(dataProjectDir, "DataBase.db"); - - var options = new DbContextOptionsBuilder() - .UseSqlite($"Data Source={dbPath}") - .Options; - - return new ApplicationDbContext(options); - } -} \ No newline at end of file diff --git a/Data/Data.csproj b/Data/Data.csproj deleted file mode 100644 index 18ab4fe..0000000 --- a/Data/Data.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - preview - Data - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - diff --git a/Data/DataContext/ApplicationDbContext.cs b/Data/DataContext/ApplicationDbContext.cs deleted file mode 100644 index f193709..0000000 --- a/Data/DataContext/ApplicationDbContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Data.models; -using Microsoft.EntityFrameworkCore; -using Task = Data.models.Task; - -namespace Data.DataContext; - -public class ApplicationDbContext : DbContext -{ - public ApplicationDbContext(DbContextOptions options) : base(options) - { - } - - public DbSet Users { get; set; } - public DbSet Events { get; set; } - public DbSet UserTeams { get; set; } - public DbSet UserTasks { get; set; } - public DbSet Teams { get; set; } - public DbSet Tasks { get; set; } - public DbSet Invitations { get; set; } - public DbSet Calendars { get; set; } -} \ No newline at end of file diff --git a/Data/UserManager.cs b/Data/UserManager.cs deleted file mode 100644 index 28c8509..0000000 --- a/Data/UserManager.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Data.Sqlite; -using Data.models; -using Microsoft.EntityFrameworkCore; -using System.Threading.Tasks; -using Data.DataContext; -using Task = System.Threading.Tasks.Task; - -namespace Data; - -public class UserManager -{ - private readonly ApplicationDbContext context; - - public UserManager(ApplicationDbContext context) - { - this.context = context; - } - - public async Task FindOrCreateUserAsync(User tgUser) - { - ArgumentNullException.ThrowIfNull(tgUser); - var user = await FindUserByTgIdAsync(tgUser.TelegramId); - if (user != null) return user; - await context.Users.AddAsync(tgUser); - await context.SaveChangesAsync(); - return tgUser; - } - - private async Task FindUserByTgIdAsync(long telegramId) - { - return await context.Users.FirstOrDefaultAsync(x => x.TelegramId == telegramId); - } - - public async Task DeleteUserAsync(long telegramId) - { - var user = await FindUserByTgIdAsync(telegramId); - if (user != null) - { - context.Users.Remove(user); - await context.SaveChangesAsync(); - } - } -} \ No newline at end of file diff --git a/Data/models/Calendar.cs b/Data/models/Calendar.cs deleted file mode 100644 index b21576f..0000000 --- a/Data/models/Calendar.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.EntityFrameworkCore.Diagnostics; - -namespace Data.models; - -public class Calendar -{ - public int Id { get; set; } - public int TeamId { get; set; } - public Team Team { get; set; } - - public ICollection Events { get; set; } - public ICollection Tasks { get; set; } -} \ No newline at end of file diff --git a/Data/models/Event.cs b/Data/models/Event.cs deleted file mode 100644 index 38422a1..0000000 --- a/Data/models/Event.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Data.models; - -public enum EventStatus -{ - Active, - Archived, -} - -public class Event -{ - public int Id { get; set; } - public string Name { get; set; } - public EventStatus Status { get; set; } - public string Description { get; set; } - public DateTime Date { get; set; } - public Calendar Calendar { get; set; } - public ICollection Tasks { get; set; } -} \ No newline at end of file diff --git a/Data/models/Invitation.cs b/Data/models/Invitation.cs deleted file mode 100644 index e8bea24..0000000 --- a/Data/models/Invitation.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Data.models; - -public enum InvitationStatus -{ - Pending, - Accepted, - Declined, -} -public class Invitation -{ - public int Id { get; set; } - public int TeamId { get; set; } - public Team Team { get; set; } - public int UserId { get; set; } - public User User { get; set; } - public InvitationStatus Status { get; set; } - -} \ No newline at end of file diff --git a/Data/models/Task.cs b/Data/models/Task.cs deleted file mode 100644 index a7822a5..0000000 --- a/Data/models/Task.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Data.models; - -public enum TaskStatus -{ - inProgress, - Review, - Completed, -} - -public class Task -{ - public int Id { get; set; } - public string Name { get; set; } - public string? Description { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime Deadline { get; set; } - public TaskStatus Status { get; set; } - - public Event Event { get; set; } - -} \ No newline at end of file diff --git a/Data/models/Team.cs b/Data/models/Team.cs deleted file mode 100644 index 049975d..0000000 --- a/Data/models/Team.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Data.models; - -public class Team -{ - public int Id { get; set; } - public string Name { get; set; } - - public Calendar Calendar { get; set; } - public ICollection UserTeams { get; set; } - public ICollection Events { get; set; } -} \ No newline at end of file diff --git a/Data/models/User.cs b/Data/models/User.cs deleted file mode 100644 index b171c11..0000000 --- a/Data/models/User.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Data.models; - -public class User -{ - public int Id { get; set; } - public long TelegramId { get; set; } - public string UserName { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public string? PhotoUrl { get; set; } - public string? Bio { get; set; } - public string Hash { get; set; } - - public ICollection Invitations { get; set; } - public ICollection Teams { get; set; } - public ICollection Tasks { get; set; } -} \ No newline at end of file diff --git a/Data/models/UserTask.cs b/Data/models/UserTask.cs deleted file mode 100644 index 18ececc..0000000 --- a/Data/models/UserTask.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Data.models; - -public enum TaskRole -{ - Executor, - None -} -public class UserTask -{ - public int Id { get; set; } - public int UserId { get; set; } - public User User { get; set; } - public int TaskId { get; set; } - public Task Task { get; set; } - public TaskRole Role { get; set; } -} \ No newline at end of file diff --git a/Data/models/UserTeam.cs b/Data/models/UserTeam.cs deleted file mode 100644 index 9475137..0000000 --- a/Data/models/UserTeam.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Data.models; - -public enum TeamRole -{ - Member, - Admin, - Owner -} - -public class UserTeam -{ - public int Id { get; set; } - public int UserId { get; set; } - public User User { get; set; } - public int TeamId { get; set; } - public Team Team { get; set; } - public TeamRole Role { get; set; } -} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/IAchievementService.cs b/SMMTracker.Application/Abstractions/IAchievementService.cs new file mode 100644 index 0000000..fa01149 --- /dev/null +++ b/SMMTracker.Application/Abstractions/IAchievementService.cs @@ -0,0 +1,9 @@ +namespace SMMTracker.Application.Abstractions; + +public interface IAchievementService +{ + System.Threading.Tasks.Task> GetAllAchievementsAsync(); + System.Threading.Tasks.Task> GetUserAchievementsAsync(int userId); + System.Threading.Tasks.Task> GetUserNewAchievementsAsync(int userId); + System.Threading.Tasks.Task> CheckAchievementsAsync(int userId); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/IApplicationDbContext.cs b/SMMTracker.Application/Abstractions/IApplicationDbContext.cs new file mode 100644 index 0000000..588b8ae --- /dev/null +++ b/SMMTracker.Application/Abstractions/IApplicationDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Domain.Entities; +using Task = SMMTracker.Domain.Entities.Task; + +namespace SMMTracker.Application.Abstractions; + +public interface IApplicationDbContext +{ + DbSet Users { get; set; } + DbSet Tasks { get; set; } + DbSet Teams { get; set; } + DbSet Calendars { get; set; } + DbSet Events { get; set; } + DbSet UserTeams { get; set; } + DbSet UserTasks { get; set; } + + // ДОБАВЬ ЭТИ ДВЕ СТРОЧКИ: + DbSet Achievements { get; set; } + DbSet UserAchievements { get; set; } + + // Обнови метод (добавь = default), чтобы не было ошибок в других местах + System.Threading.Tasks.Task SaveChangesAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/ICalendarService.cs b/SMMTracker.Application/Abstractions/ICalendarService.cs new file mode 100644 index 0000000..4754ab3 --- /dev/null +++ b/SMMTracker.Application/Abstractions/ICalendarService.cs @@ -0,0 +1,12 @@ +using SMMTracker.Application.Dtos; + +namespace SMMTracker.Application.Abstractions; + +public interface ICalendarService +{ + Task<(int CalendarId, int TeamId)> GetCalendarInfoByUserIdAsync(string userId, + CancellationToken cancellationToken = default); + + Task CreateCalendarAsync(CreateCalendarDto dto, CancellationToken cancellationToken = default); + Task GetCalendarIdByUserIdAsync(string userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/IEventService.cs b/SMMTracker.Application/Abstractions/IEventService.cs new file mode 100644 index 0000000..a95c8a6 --- /dev/null +++ b/SMMTracker.Application/Abstractions/IEventService.cs @@ -0,0 +1,14 @@ +using SMMTracker.Application.Dtos; + +namespace SMMTracker.Application.Abstractions; + +public interface IEventService +{ + Task CreateEventAsync(CreateEventDto dto, CancellationToken cancellationToken = default); + + Task> GetEventsForMonthAsync(int calendarId, int month, int year, + CancellationToken cancellationToken = default); + + Task GetEventDetailsAsync(int eventId, CancellationToken cancellationToken = default); + Task> GetEventsForTeamAsync(int teamId); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/ITaskService.cs b/SMMTracker.Application/Abstractions/ITaskService.cs new file mode 100644 index 0000000..290d279 --- /dev/null +++ b/SMMTracker.Application/Abstractions/ITaskService.cs @@ -0,0 +1,18 @@ +using SMMTracker.Application.Dtos; +using TaskEntity = SMMTracker.Domain.Entities.Task; + +namespace SMMTracker.Application.Abstractions; + +public interface ITaskService +{ + // Используем полные имена типов, чтобы не было конфликтов + System.Threading.Tasks.Task CreateTaskAsync(CreateTaskDto taskDto, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task MoveTaskToReviewAsync(int taskId, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task MoveTaskToDoneAsync(int taskId, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task DeleteTaskAsync(int taskId, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task ChangeTaskNameAsync(int taskId, string name, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task ChangeTaskDescriptionAsync(int taskId, string description, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task SetTaskDeadlineAsync(int taskId, DateTime deadline, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task AssignUserToTaskAsync(int taskId, int userIdToAssign, int adminId, CancellationToken cancellationToken = default); + System.Threading.Tasks.Task MoveTaskToProgressAsync(int taskId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/ITeamService.cs b/SMMTracker.Application/Abstractions/ITeamService.cs new file mode 100644 index 0000000..eec31a0 --- /dev/null +++ b/SMMTracker.Application/Abstractions/ITeamService.cs @@ -0,0 +1,24 @@ +using SMMTracker.Application.Dtos; +using Task = System.Threading.Tasks.Task; +using System.Threading; + +namespace SMMTracker.Application.Abstractions; + +public interface ITeamService +{ + Task UpdateTeamAsync(int teamId, string newName, string newDescription, int adminId, + CancellationToken cancellationToken = default); + + Task> GetTeamMembersAsync(int teamId); + Task IsUserAdminAsync(int teamId, int userId); + Task GetTeamDetailsAsync(int teamId); + Task CreateTeamAsync(CreateTeamDto dto, int creatorId, CancellationToken cancellationToken = default); + Task JoinTeamAsync(JoinTeamDto dto, CancellationToken cancellationToken = default); + + Task RemoveUserFromTeamAsync(int teamId, int userIdToRemove, int adminId, + CancellationToken cancellationToken = default); + + Task LeaveTeamAsync(int teamId, int userId, CancellationToken cancellationToken = default); + Task> GetTeamsForUserAsync(int userId, CancellationToken cancellationToken = default); + Task GetCalendarForTeamAsync(int teamId); +} \ No newline at end of file diff --git a/SMMTracker.Application/Abstractions/IUserService.cs b/SMMTracker.Application/Abstractions/IUserService.cs new file mode 100644 index 0000000..1cccd08 --- /dev/null +++ b/SMMTracker.Application/Abstractions/IUserService.cs @@ -0,0 +1,14 @@ +using SMMTracker.Application.Dtos; +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Application.Abstractions; + +public interface IUserService +{ + Task FindOrCreateUserAsync(User userClaim); + Task GetUserByIdAsync(int userId); + Task GetUserProfileAsync(int userId); + Task UpdateUserProfileAsync(int userId, UserProfileDto dto); + Task GetUserByUsernameAsync(string username); +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/AssignUserDto.cs b/SMMTracker.Application/Dtos/AssignUserDto.cs new file mode 100644 index 0000000..134c9e6 --- /dev/null +++ b/SMMTracker.Application/Dtos/AssignUserDto.cs @@ -0,0 +1,7 @@ +namespace SMMTracker.Application.Dtos; + +public class AssignUserDto +{ + public int UserId { get; set; } + public int AdminId { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/CalendarDto.cs b/SMMTracker.Application/Dtos/CalendarDto.cs new file mode 100644 index 0000000..d6b1122 --- /dev/null +++ b/SMMTracker.Application/Dtos/CalendarDto.cs @@ -0,0 +1,6 @@ +namespace SMMTracker.Application.Dtos; + +public class CalendarDto +{ + public int Id { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/ChangeTaskDescriptionDto.cs b/SMMTracker.Application/Dtos/ChangeTaskDescriptionDto.cs new file mode 100644 index 0000000..cc6ebd3 --- /dev/null +++ b/SMMTracker.Application/Dtos/ChangeTaskDescriptionDto.cs @@ -0,0 +1,6 @@ +namespace SMMTracker.Application.Dtos; + +public class ChangeTaskDescriptionDto +{ + public string Description { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/ChangeTaskNameDto.cs b/SMMTracker.Application/Dtos/ChangeTaskNameDto.cs new file mode 100644 index 0000000..76f4315 --- /dev/null +++ b/SMMTracker.Application/Dtos/ChangeTaskNameDto.cs @@ -0,0 +1,6 @@ +namespace SMMTracker.Application.Dtos; + +public class ChangeTaskNameDto +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/CreateCalendarDto.cs b/SMMTracker.Application/Dtos/CreateCalendarDto.cs index c883045..c37de45 100644 --- a/SMMTracker.Application/Dtos/CreateCalendarDto.cs +++ b/SMMTracker.Application/Dtos/CreateCalendarDto.cs @@ -1,8 +1,6 @@ -using SMMTracker.Domain.Entities; - namespace SMMTracker.Application.Dtos; public class CreateCalendarDto { - public int TeamId{ get; set; } + public int TeamId { get; set; } } \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/CreateEventDto.cs b/SMMTracker.Application/Dtos/CreateEventDto.cs index 3528008..dc98061 100644 --- a/SMMTracker.Application/Dtos/CreateEventDto.cs +++ b/SMMTracker.Application/Dtos/CreateEventDto.cs @@ -5,5 +5,6 @@ public class CreateEventDto public string? Name { get; set; } public string? Description { get; set; } public DateTime Date { get; set; } - public int CalendarId { get; set; } + public int TeamId { get; set; } + public int CreatedBy { get; set; } } \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/CreateTaskDto.cs b/SMMTracker.Application/Dtos/CreateTaskDto.cs index f8fde0c..5337a5a 100644 --- a/SMMTracker.Application/Dtos/CreateTaskDto.cs +++ b/SMMTracker.Application/Dtos/CreateTaskDto.cs @@ -5,5 +5,5 @@ public class CreateTaskDto public string Name { get; set; } public string? Description { get; set; } public int EventId { get; set; } - public int? CalendarId { get; set; } + public int AssignedUserId { get; set; } } \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/CreateTeamDto.cs b/SMMTracker.Application/Dtos/CreateTeamDto.cs index 3fc51ab..e88a27b 100644 --- a/SMMTracker.Application/Dtos/CreateTeamDto.cs +++ b/SMMTracker.Application/Dtos/CreateTeamDto.cs @@ -3,4 +3,5 @@ namespace SMMTracker.Application.Dtos; public class CreateTeamDto { public string Name { get; set; } + public string? Description { get; set; } } \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/EventDetailsDto.cs b/SMMTracker.Application/Dtos/EventDetailsDto.cs new file mode 100644 index 0000000..c31cf76 --- /dev/null +++ b/SMMTracker.Application/Dtos/EventDetailsDto.cs @@ -0,0 +1,13 @@ +namespace SMMTracker.Application.Dtos; + +public class EventDetailsDto +{ + public int Id { get; set; } + public int TeamId { get; set; } + public string Name { get; set; } = ""; + public string? Description { get; set; } + public DateTime Date { get; set; } + public DateTime CreatedAt { get; set; } + public int CreatedBy { get; set; } + public List Tasks { get; set; } = new(); +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/EventSummaryDto.cs b/SMMTracker.Application/Dtos/EventSummaryDto.cs new file mode 100644 index 0000000..f1976db --- /dev/null +++ b/SMMTracker.Application/Dtos/EventSummaryDto.cs @@ -0,0 +1,11 @@ +namespace SMMTracker.Application.Dtos; + +public class EventSummaryDto +{ + public int Id { get; set; } + public string Name { get; set; } + public DateTime Date { get; set; } + public string Description { get; set; } = ""; + public int CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/JoinTeamDto.cs b/SMMTracker.Application/Dtos/JoinTeamDto.cs new file mode 100644 index 0000000..8c9017c --- /dev/null +++ b/SMMTracker.Application/Dtos/JoinTeamDto.cs @@ -0,0 +1,7 @@ +namespace SMMTracker.Application.Dtos; + +public class JoinTeamDto +{ + public int UserId { get; set; } + public string Code { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/LeaveTeamDto.cs b/SMMTracker.Application/Dtos/LeaveTeamDto.cs new file mode 100644 index 0000000..931140c --- /dev/null +++ b/SMMTracker.Application/Dtos/LeaveTeamDto.cs @@ -0,0 +1,6 @@ +namespace SMMTracker.Application.Dtos; + +public class LeaveTeamDto +{ + public int UserId { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/RemoveUserDto.cs b/SMMTracker.Application/Dtos/RemoveUserDto.cs new file mode 100644 index 0000000..f671104 --- /dev/null +++ b/SMMTracker.Application/Dtos/RemoveUserDto.cs @@ -0,0 +1,7 @@ +namespace SMMTracker.Application.Dtos; + +public class RemoveUserDto +{ + public int UserId { get; set; } + public int AdminId { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/SetTaskDeadlineDto.cs b/SMMTracker.Application/Dtos/SetTaskDeadlineDto.cs new file mode 100644 index 0000000..9871ad9 --- /dev/null +++ b/SMMTracker.Application/Dtos/SetTaskDeadlineDto.cs @@ -0,0 +1,6 @@ +namespace SMMTracker.Application.Dtos; + +public class SetTaskDeadlineDto +{ + public DateTime Deadline { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/TaskSummaryDto.cs b/SMMTracker.Application/Dtos/TaskSummaryDto.cs new file mode 100644 index 0000000..83106e2 --- /dev/null +++ b/SMMTracker.Application/Dtos/TaskSummaryDto.cs @@ -0,0 +1,12 @@ +using TaskStatus = SMMTracker.Domain.Enums.TaskStatus; + +namespace SMMTracker.Application.Dtos; + +public class TaskSummaryDto +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public TaskStatus Status { get; set; } + public string AssignedUserName { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/TeamDetailsDto.cs b/SMMTracker.Application/Dtos/TeamDetailsDto.cs new file mode 100644 index 0000000..4c88a3c --- /dev/null +++ b/SMMTracker.Application/Dtos/TeamDetailsDto.cs @@ -0,0 +1,10 @@ +namespace SMMTracker.Application.Dtos; + +public class TeamDetailsDto +{ + public int Id { get; set; } + public string Name { get; set; } + public string InvitationCode { get; set; } + public string Description { get; set; } + public List Members { get; set; } = new(); +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/TeamDto.cs b/SMMTracker.Application/Dtos/TeamDto.cs new file mode 100644 index 0000000..0ffb633 --- /dev/null +++ b/SMMTracker.Application/Dtos/TeamDto.cs @@ -0,0 +1,13 @@ +using SMMTracker.Domain.Enums; + +namespace SMMTracker.Application.Dtos; + +public class TeamDto +{ + public int Id { get; set; } + public string Name { get; set; } + public bool IsOwner { get; set; } + public string InvitationCode { get; set; } + public DateTime CreatedAt { get; set; } + public int MemberCount { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/TeamMemberDto.cs b/SMMTracker.Application/Dtos/TeamMemberDto.cs new file mode 100644 index 0000000..f725761 --- /dev/null +++ b/SMMTracker.Application/Dtos/TeamMemberDto.cs @@ -0,0 +1,12 @@ +using SMMTracker.Domain.Enums; + +namespace SMMTracker.Application.Dtos; + +public class TeamMemberDto +{ + public int UserId { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Username { get; set; } + public string Role { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/DTOs/TelegramLoginData.cs b/SMMTracker.Application/Dtos/TelegramLoginDto.cs similarity index 79% rename from SMMTracker.WebUI/DTOs/TelegramLoginData.cs rename to SMMTracker.Application/Dtos/TelegramLoginDto.cs index c32efc7..3894c45 100644 --- a/SMMTracker.WebUI/DTOs/TelegramLoginData.cs +++ b/SMMTracker.Application/Dtos/TelegramLoginDto.cs @@ -1,6 +1,6 @@ -namespace BlazorApp1.DTOs; +namespace SMMTracker.Application.Dtos; -public class TelegramLoginData +public class TelegramLoginDto { public long Id { get; set; } public string FirstName { get; set; } = ""; diff --git a/SMMTracker.Application/Dtos/UserDto.cs b/SMMTracker.Application/Dtos/UserDto.cs index 1150e66..a79bf3b 100644 --- a/SMMTracker.Application/Dtos/UserDto.cs +++ b/SMMTracker.Application/Dtos/UserDto.cs @@ -1,11 +1,11 @@ namespace SMMTracker.Application.Dtos; -public record UserDto +public class UserDto { public int Id { get; set; } - public long TelegramId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } - public string Username { get; set; } - public string TelegramUsername { get; set; } + public string UserName { get; set; } + public long TelegramId { get; set; } + public string? ProfileDescription { get; set; } } \ No newline at end of file diff --git a/SMMTracker.Application/Dtos/UserProfileDto.cs b/SMMTracker.Application/Dtos/UserProfileDto.cs new file mode 100644 index 0000000..04c0c94 --- /dev/null +++ b/SMMTracker.Application/Dtos/UserProfileDto.cs @@ -0,0 +1,8 @@ +namespace SMMTracker.Application.Dtos; + +public class UserProfileDto +{ + public string FirstName { get; set; } + public string LastName { get; set; } + public string Description { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.Application/Interfaces/IApplicationDbContext.cs b/SMMTracker.Application/Interfaces/IApplicationDbContext.cs deleted file mode 100644 index b66cacc..0000000 --- a/SMMTracker.Application/Interfaces/IApplicationDbContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -using SMMTracker.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Task = SMMTracker.Domain.Entities.Task; - -namespace SMMTracker.Application.Interfaces; - -public interface IApplicationDbContext -{ - DbSet Users { get; set; } - DbSet Tasks { get; set; } - DbSet Teams { get; set; } - DbSet Calendars { get; set; } - DbSet Events { get; set; } - Task SaveChangesAsync(CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/SMMTracker.Application/Interfaces/IUserManager.cs b/SMMTracker.Application/Interfaces/IUserManager.cs deleted file mode 100644 index bc86f45..0000000 --- a/SMMTracker.Application/Interfaces/IUserManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -using SMMTracker.Application.Dtos; -using SMMTracker.Domain.Entities; - -namespace SMMTracker.Application.Interfaces; - -public interface IUserManager -{ - Task FindOrCreateUserAsync(User user); - -} \ No newline at end of file diff --git a/SMMTracker.Application/Interfaces/Repositories/IUserRepository.cs b/SMMTracker.Application/Interfaces/Repositories/IUserRepository.cs deleted file mode 100644 index 73d50be..0000000 --- a/SMMTracker.Application/Interfaces/Repositories/IUserRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using SMMTracker.Domain.Entities; -using Task = System.Threading.Tasks.Task; - -namespace SMMTracker.Application.Interfaces.Repositories; - -public interface IUserRepository -{ - Task GetByTelegramIdAsync(long telegramId); - Task AddAsync(User user); - Task UpdateAsync(User user); -} \ No newline at end of file diff --git a/SMMTracker.Application/Services/AchievementService.cs b/SMMTracker.Application/Services/AchievementService.cs new file mode 100644 index 0000000..9176fa2 --- /dev/null +++ b/SMMTracker.Application/Services/AchievementService.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; + +namespace SMMTracker.Application.Services; + +public class AchievementService : IAchievementService +{ + private readonly IApplicationDbContext _context; + + public AchievementService(IApplicationDbContext context) + { + _context = context; + } + + // ТЕПЕРЬ ВОЗВРАЩАЕТ СПИСОК ПОЛУЧЕННЫХ АЧИВОК + public async System.Threading.Tasks.Task> CheckAchievementsAsync(int userId) + { + var teamsCount = await _context.UserTeams.Where(ut => ut.UserId == userId).CountAsync(); + var allAchievements = await _context.Achievements.ToListAsync(); + var userAchievementIds = await _context.UserAchievements + .Where(ua => ua.UserId == userId) + .Select(ua => ua.AchievementId) + .ToListAsync(); + + var newAchievements = allAchievements + .Where(a => !userAchievementIds.Contains(a.Id) && teamsCount >= a.TasksThreshold) + .ToList(); + + if (newAchievements.Any()) + { + foreach (var achievement in newAchievements) + { + _context.UserAchievements.Add(new UserAchievement + { + UserId = userId, + AchievementId = achievement.Id, + DateReceived = DateTime.Now + }); + } + await _context.SaveChangesAsync(); + } + + return newAchievements; // Возвращаем список для анимации + } + + public async System.Threading.Tasks.Task> GetAllAchievementsAsync() + { + return await _context.Achievements.ToListAsync(); + } + + public async System.Threading.Tasks.Task> GetUserAchievementsAsync(int userId) + { + var achievementIds = await _context.UserAchievements + .Where(ua => ua.UserId == userId) + .Select(ua => ua.AchievementId) + .ToListAsync(); + + return await _context.Achievements.Where(a => achievementIds.Contains(a.Id)).ToListAsync(); + } + + public async System.Threading.Tasks.Task> GetUserNewAchievementsAsync(int userId) + { + return new List(); + } +} \ No newline at end of file diff --git a/SMMTracker.Application/Services/CalendarService.cs b/SMMTracker.Application/Services/CalendarService.cs index b66cff4..df08b0a 100644 --- a/SMMTracker.Application/Services/CalendarService.cs +++ b/SMMTracker.Application/Services/CalendarService.cs @@ -1,31 +1,43 @@ -using Microsoft.EntityFrameworkCore; -using SMMTracker.Domain.Entities; +using SMMTracker.Application.Abstractions; using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; namespace SMMTracker.Application.Services; -public class CalendarService +public class CalendarService : ICalendarService { - private readonly IApplicationDbContext _context; + private readonly ICalendarRepository _calendarRepository; + private readonly ITeamRepository _teamRepository; + + public CalendarService(ICalendarRepository calendarRepository, ITeamRepository teamRepository) + { + _calendarRepository = calendarRepository; + _teamRepository = teamRepository; + } - public CalendarService(IApplicationDbContext context) + public async Task GetCalendarIdByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + var calendar = await _calendarRepository.GetByUserIdAsync(userId, cancellationToken); + return calendar?.Id ?? 0; + } + + public async Task<(int CalendarId, int TeamId)> GetCalendarInfoByUserIdAsync(string userId, + CancellationToken cancellationToken = default) { - _context = context; + var calendar = await _calendarRepository.GetByUserIdAsync(userId, cancellationToken); + return (calendar?.Id ?? 0, calendar?.TeamId ?? 0); } public async Task CreateCalendarAsync(CreateCalendarDto dto, CancellationToken cancellationToken = default) { - var teamExists = await _context.Teams.AnyAsync(t => t.Id == dto.TeamId, cancellationToken); + var teamExists = await _teamRepository.ExistsAsync(dto.TeamId, cancellationToken); if (!teamExists) - { throw new Exception($"Команда с Id={dto.TeamId} не найдена."); - } var calendar = new Calendar(dto.TeamId); - _context.Calendars.Add(calendar); - await _context.SaveChangesAsync(cancellationToken); + await _calendarRepository.AddAsync(calendar, cancellationToken); return calendar.Id; } } \ No newline at end of file diff --git a/SMMTracker.Application/Services/EventService.cs b/SMMTracker.Application/Services/EventService.cs index a2ccb47..6295945 100644 --- a/SMMTracker.Application/Services/EventService.cs +++ b/SMMTracker.Application/Services/EventService.cs @@ -1,30 +1,98 @@ -using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; using SMMTracker.Domain.Entities; using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; +using SMMTracker.Domain.IRepositories; namespace SMMTracker.Application.Services; -public class EventService +public class EventService : IEventService { - private readonly IApplicationDbContext _context; + private readonly IEventRepository _eventRepository; + private readonly ICalendarRepository _calendarRepository; - public EventService(IApplicationDbContext context) + public EventService(IEventRepository eventRepository, ICalendarRepository calendarRepository) { - _context = context; + _eventRepository = eventRepository; + _calendarRepository = calendarRepository; } public async Task CreateEventAsync(CreateEventDto dto, CancellationToken cancellationToken = default) { + var calendar = await _calendarRepository.GetByTeamIdAsync(dto.TeamId, cancellationToken); + if (calendar == null) + throw new InvalidOperationException($"Не найден календарь для команды с Id={dto.TeamId}"); + var eventAs = new Event( dto.Name, dto.Description, dto.Date, - dto.CalendarId + calendar.Id, + dto.CreatedBy, + calendar.TeamId ); - _context.Events.Add(eventAs); - await _context.SaveChangesAsync(cancellationToken); + await _eventRepository.AddAsync(eventAs, cancellationToken); return eventAs.Id; } + + public async Task> GetEventsForMonthAsync(int calendarId, int month, int year, + CancellationToken cancellationToken = default) + { + var events = await _eventRepository.GetEventsForMonthAsync(calendarId, month, year, cancellationToken); + + return events + .Select(e => new EventSummaryDto + { + Id = e.Id, + Name = e.Name, + Date = e.Date, + CreatedBy = e.CreatedBy, + }) + .ToList(); + } + + public async Task GetEventDetailsAsync(int eventId, CancellationToken cancellationToken = default) + { + var eventEntity = await _eventRepository.GetByIdAsync(eventId, cancellationToken); + + if (eventEntity == null) + return null; + + return new EventDetailsDto + { + Id = eventEntity.Id, + Name = eventEntity.Name, + Description = eventEntity.Description, + Date = eventEntity.Date, + CreatedAt = eventEntity.CreatedAt, + CreatedBy = eventEntity.CreatedBy, + Tasks = eventEntity.Tasks.Select(t => new TaskSummaryDto + { + Id = t.Id, + Name = t.Name, + Description = t.Description ?? "", + Status = t.Status + }).ToList() + }; + } + + public async Task> GetEventsForTeamAsync(int teamId) + { + var calendar = await _calendarRepository.GetByTeamIdAsync(teamId); + if (calendar == null) + { + return []; + } + + var events = await _eventRepository.GetEventsForCalendarAsync(calendar.Id); + return events.Select(e => new EventSummaryDto + { + Id = e.Id, + Name = e.Name, + Date = e.Date, + Description = e.Description, + CreatedAt = e.CreatedAt, + CreatedBy = e.CreatedBy + }).ToList(); + } } \ No newline at end of file diff --git a/SMMTracker.Application/Services/TaskService.cs b/SMMTracker.Application/Services/TaskService.cs index 319831e..f16c38f 100644 --- a/SMMTracker.Application/Services/TaskService.cs +++ b/SMMTracker.Application/Services/TaskService.cs @@ -1,59 +1,146 @@ +using SMMTracker.Application.Abstractions; using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.Enums; +using SMMTracker.Domain.IRepositories; using Task = SMMTracker.Domain.Entities.Task; +using TaskStatus = SMMTracker.Domain.Enums.TaskStatus; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; namespace SMMTracker.Application.Services; -public class TaskService +public class TaskService : ITaskService { - private readonly IApplicationDbContext _context; + private readonly ITaskRepository _taskRepository; + private readonly IUserTeamRepository _userTeamRepository; + private readonly IUserTaskRepository _userTaskRepository; + private readonly IEventRepository _eventRepository; + private readonly IApplicationDbContext _context; // Нужен для быстрого поиска исполнителей - public TaskService(IApplicationDbContext context) + public TaskService( + ITaskRepository taskRepository, + IUserTeamRepository userTeamRepository, + IUserTaskRepository userTaskRepository, + IEventRepository eventRepository, + IAchievementService achievementService, // Должно быть здесь + IApplicationDbContext context) // Должно быть здесь { + _taskRepository = taskRepository; + _userTeamRepository = userTeamRepository; + _userTaskRepository = userTaskRepository; + _eventRepository = eventRepository; _context = context; } - public async Task CreateTaskAsync(CreateTaskDto taskDto, + public async Task CreateTaskAsync(CreateTaskDto dto, CancellationToken cancellationToken = default) { + var parentEvent = await _eventRepository.GetByIdAsync(dto.EventId, cancellationToken); + if (parentEvent == null) + throw new InvalidOperationException($"Событие с Id={dto.EventId} не найдено"); + var task = new Task( - taskDto.Name, - taskDto.Description, - taskDto.EventId, - taskDto.CalendarId - ); - _context.Tasks.Add(task); - await _context.SaveChangesAsync(cancellationToken); + dto.Name, + dto.Description, + dto.EventId, + parentEvent.CalendarId + ) + { + Status = TaskStatus.InProgress + }; + + await _taskRepository.AddAsync(task, cancellationToken); + return task.Id; } public async System.Threading.Tasks.Task MoveTaskToReviewAsync(int taskId, CancellationToken cancellationToken = default) { - var task = await _context.Tasks.FindAsync([taskId], cancellationToken); + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); if (task == null) throw new Exception("Task not found"); - task.MoveToReview(); - await _context.SaveChangesAsync(cancellationToken); + + await _taskRepository.UpdateStatusToReviewAsync(task, cancellationToken); } public async System.Threading.Tasks.Task MoveTaskToDoneAsync(int taskId, CancellationToken cancellationToken = default) { - var task = await _context.Tasks.FindAsync([taskId], cancellationToken); + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); + if (task == null) + throw new Exception("Task not found"); + + await _taskRepository.UpdateStatusToDoneAsync(task, cancellationToken); + + } + + public async System.Threading.Tasks.Task MoveTaskToProgressAsync(int taskId, + CancellationToken cancellationToken = default) + { + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); if (task == null) throw new Exception("Task not found"); - task.MoveToDone(); - await _context.SaveChangesAsync(cancellationToken); + + await _taskRepository.UpdateStatusToInProgressAsync(task, cancellationToken); } - public async System.Threading.Tasks.Task RemoveTaskAsync(int taskId, + public async System.Threading.Tasks.Task DeleteTaskAsync(int taskId, CancellationToken cancellationToken = default) { - var task = await _context.Tasks.FindAsync([taskId], cancellationToken); + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); if (task == null) throw new Exception("Task not found"); - _context.Tasks.Remove(task); - await _context.SaveChangesAsync(cancellationToken); + + await _taskRepository.DeleteAsync(taskId, cancellationToken); + } + + public async System.Threading.Tasks.Task ChangeTaskNameAsync(int taskId, string name, + CancellationToken cancellationToken = default) + { + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); + if (task == null) + throw new Exception("Task not found"); + + await _taskRepository.ChangeTaskNameAsync(task, name, cancellationToken); + } + + public async System.Threading.Tasks.Task ChangeTaskDescriptionAsync(int taskId, string description, + CancellationToken cancellationToken = default) + { + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); + if (task == null) + throw new Exception("Task not found"); + + await _taskRepository.ChangeTaskDescriptionAsync(task, description, cancellationToken); + } + + public async System.Threading.Tasks.Task SetTaskDeadlineAsync(int taskId, DateTime deadline, + CancellationToken cancellationToken = default) + { + var task = await _taskRepository.GetByIdAsync(taskId, cancellationToken); + if (task == null) + throw new Exception("Task not found"); + } + + public async System.Threading.Tasks.Task AssignUserToTaskAsync(int taskId, int userIdToAssign, int adminId, + CancellationToken cancellationToken = default) + { + var task = await _taskRepository.GetByIdWithEventAndTeamAsync(taskId, cancellationToken); + + if (task == null) + throw new Exception("Task not found"); + + if (!await _userTeamRepository.IsUserAdminAsync(task.Event.Team.Id, adminId, cancellationToken)) + throw new UnauthorizedAccessException("Only admins can assign users to tasks"); + + var userTask = new UserTask + { + TaskId = task.Id, + UserId = userIdToAssign, + }; + + await _userTaskRepository.AddAsync(userTask, cancellationToken); } } \ No newline at end of file diff --git a/SMMTracker.Application/Services/TeamService.cs b/SMMTracker.Application/Services/TeamService.cs index 21df704..963ddd4 100644 --- a/SMMTracker.Application/Services/TeamService.cs +++ b/SMMTracker.Application/Services/TeamService.cs @@ -1,24 +1,214 @@ +using SMMTracker.Application.Abstractions; using SMMTracker.Domain.Entities; +using SMMTracker.Application.Dtos; +using SMMTracker.Domain.Enums; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; namespace SMMTracker.Application.Services; -using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; - -public class TeamService +public class TeamService : ITeamService { - private readonly IApplicationDbContext _context; + private readonly ITeamRepository _teamRepository; + private readonly IUserTeamRepository _userTeamRepository; + private readonly ICalendarRepository _calendarRepository; + private readonly SMMTracker.Application.Abstractions.IAchievementService _achievementService; - public TeamService(IApplicationDbContext context) + public TeamService( + ITeamRepository teamRepository, + IUserTeamRepository userTeamRepository, + ICalendarRepository calendarRepository, + SMMTracker.Application.Abstractions.IAchievementService achievementService) // Укажи полный путь здесь { - _context = context; + _teamRepository = teamRepository; + _userTeamRepository = userTeamRepository; + _calendarRepository = calendarRepository; + _achievementService = achievementService; } - public async Task CreateTeamAsync(CreateTeamDto dto, CancellationToken cancellationToken = default) + public async Task CreateTeamAsync(CreateTeamDto dto, int creatorId, + CancellationToken cancellationToken = default) { - var team = new Team(dto.Name); - _context.Teams.Add(team); - await _context.SaveChangesAsync(cancellationToken); + var code = GenerateTeamCode(); + while (await _teamRepository.ExistsByCodeAsync(code)) + { + code = GenerateTeamCode(); + } + + var team = new Team(dto.Name, code, dto.Description); + await _teamRepository.AddAsync(team); + + var calendar = new Calendar(team.Id); + await _calendarRepository.AddAsync(calendar); + + var userTeam = new UserTeam + { + TeamId = team.Id, + UserId = creatorId, + Role = TeamRole.Admin, + Team = team + }; + + team.UserTeams.Add(userTeam); + + await _userTeamRepository.AddAsync(userTeam); + await _achievementService.CheckAchievementsAsync(creatorId); + return team.Id; } + + + private static string GenerateTeamCode() + { + const string symbols = "ABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + return new string(Enumerable + .Repeat(symbols, 5) + .Select(s => s[random.Next(s.Length)]) + .ToArray()); + } + + public async Task JoinTeamAsync(JoinTeamDto dto, CancellationToken cancellationToken = default) + { + var team = await _teamRepository.GetByCodeAsync(dto.Code); + + if (team == null) + return false; + + var userTeam = new UserTeam + { + UserId = dto.UserId, + TeamId = team.Id, + Role = TeamRole.User, + }; + + await _userTeamRepository.AddAsync(userTeam); + + await _achievementService.CheckAchievementsAsync(dto.UserId); + + return true; + } + + public async Task RemoveUserFromTeamAsync(int teamId, int userIdToRemove, int adminId, + CancellationToken cancellationToken = default) + { + if (!await _teamRepository.ExistsAsync(teamId)) + throw new Exception("Team not found"); + + if (!await _userTeamRepository.IsUserAdminAsync(teamId, adminId)) + throw new UnauthorizedAccessException("Only admins can remove users from team"); + + var userTeam = await _userTeamRepository.GetUserTeamAsync(teamId, userIdToRemove); + + if (userTeam == null) + throw new Exception("User is not in the team"); + + await _userTeamRepository.DeleteAsync(userTeam.Id); + } + + public async Task LeaveTeamAsync(int teamId, int userId, CancellationToken cancellationToken = default) + { + var userTeam = await _userTeamRepository.GetUserTeamAsync(teamId, userId); + + if (userTeam == null) + return false; + + if (userTeam.Role == TeamRole.Admin) + throw new Exception("Admin cannot leave team"); + + await _userTeamRepository.DeleteAsync(userTeam.Id); + return true; + } + + public async Task> GetTeamsForUserAsync(int userId, CancellationToken cancellationToken = default) + { + var userTeams = await _userTeamRepository.GetByUserIdAsync(userId); + + return userTeams.Select(ut => new TeamDto + { + Id = ut.Team.Id, + Name = ut.Team.Name, + IsOwner = ut.Role == TeamRole.Admin, + InvitationCode = ut.Team.Code, + }).ToList(); + } + + public async Task> GetTeamMembersAsync(int teamId) + { + var team = await _teamRepository.GetByIdWithMembersAsync(teamId); + if (team == null) return new List(); + + return team.UserTeams.Select(ut => new TeamMemberDto + { + UserId = ut.UserId, + FirstName = ut.User?.FirstName ?? "", + LastName = ut.User?.LastName ?? "", + Username = ut.User?.UserName ?? "", + Role = ut.Role switch + { + TeamRole.Admin => "Админ", + TeamRole.User => "Участник", + _ => "Участник" + } + }).ToList(); + } + + public async Task IsUserAdminAsync(int teamId, int userId) + { + return await _userTeamRepository.IsUserAdminAsync(teamId, userId); + } + + public async Task GetTeamDetailsAsync(int teamId) + { + var team = await _teamRepository.GetByIdWithMembersAsync(teamId); + + if (team == null) + return null; + + var teamDetailsDto = new TeamDetailsDto + { + Id = team.Id, + Name = team.Name, + Description = team.Description, + InvitationCode = team.Code, + Members = team.UserTeams.Select(ut => new TeamMemberDto + { + UserId = ut.UserId, + FirstName = ut.User?.FirstName ?? "", + LastName = ut.User?.LastName ?? "", + Username = ut.User?.UserName ?? "", + Role = ut.Role switch + { + TeamRole.Admin => "Админ", + TeamRole.User => "Участник", + _ => "Участник" + } + }).ToList() + }; + + return teamDetailsDto; + } + + public async Task UpdateTeamAsync(int teamId, string newName, string newDescription, int adminId, + CancellationToken cancellationToken = default) + { + var team = await _teamRepository.GetByIdAsync(teamId); + if (team == null) + throw new Exception("Team not found"); + + if (!await _userTeamRepository.IsUserAdminAsync(teamId, adminId)) + throw new UnauthorizedAccessException("Only admins can update the team"); + + team.Name = newName; + team.Description = newDescription; + await _teamRepository.UpdateAsync(team); + } + + + public async Task GetCalendarForTeamAsync(int teamId) + { + var calendar = await _calendarRepository.GetByTeamIdAsync(teamId); + + return calendar == null ? null : new CalendarDto { Id = calendar.Id }; + } } \ No newline at end of file diff --git a/SMMTracker.Application/Services/UserService.cs b/SMMTracker.Application/Services/UserService.cs new file mode 100644 index 0000000..465bf44 --- /dev/null +++ b/SMMTracker.Application/Services/UserService.cs @@ -0,0 +1,104 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Application.Services; + +public class UserService : IUserService +{ + private readonly IUserRepository _userRepository; + + public UserService(IUserRepository userRepository) + { + _userRepository = userRepository; + } + + public async Task FindOrCreateUserAsync(User userClaim) + { + var user = await _userRepository.GetByTelegramIdAsync(userClaim.TelegramId); + + if (user != null) + return GetUserDto(user); + + user = User.Create(userClaim); + await _userRepository.AddAsync(user); + + return GetUserDto(user); + } + + public async Task GetUserByIdAsync(int userId) + { + var user = await _userRepository.GetByIdAsync(userId); + if (user == null) + return null; + + return new UserDto + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + UserName = user.UserName, + TelegramId = user.TelegramId, + ProfileDescription = user.ProfileDescription + }; + } + + private static UserDto GetUserDto(User user) + { + return new UserDto + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + UserName = user.UserName, + TelegramId = user.TelegramId + }; + } + + public async Task GetUserProfileAsync(int userId) + { + var user = await _userRepository.GetByIdAsync(userId); + + if (user == null) + throw new KeyNotFoundException($"Пользователь Id{userId} не найден"); + + return new UserProfileDto() + { + FirstName = user.FirstName, + LastName = user.LastName, + Description = user.ProfileDescription ?? "" + }; + } + + + public async Task UpdateUserProfileAsync(int userId, UserProfileDto dto) + { + var user = await _userRepository.GetByIdAsync(userId); + + if (user == null) + throw new KeyNotFoundException($"Пользователь Id {userId} не найден"); + + user.UpdateUserProfile(dto.FirstName, dto.LastName, dto.Description); + + await _userRepository.UpdateAsync(user); + } + + public async Task GetUserByUsernameAsync(string username) + { + var user = await _userRepository.GetByUsernameAsync(username); + if (user == null) return null; + + return new UserDto + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + UserName = user.UserName, + TelegramId = user.TelegramId, + ProfileDescription = user.ProfileDescription + }; + } +} \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/Achievement.cs b/SMMTracker.Domain/Entities/Achievement.cs new file mode 100644 index 0000000..f1b460c --- /dev/null +++ b/SMMTracker.Domain/Entities/Achievement.cs @@ -0,0 +1,21 @@ +using SMMTracker.Domain.Entities; + +namespace SMMTracker.Domain.Entities; + +public class Achievement : Entity +{ + public string Title { get; set; } // Например "Новичок" + public string Description { get; set; } // "Выполнить 10 задач" + public string IconClass { get; set; } // Класс иконки Bootstrap/FontAwesome, например "bi-star-fill" + public int TasksThreshold { get; set; } // Порог задач: 1, 10, 50... + + public Achievement(string title, string description, string iconClass, int tasksThreshold) + { + Title = title; + Description = description; + IconClass = iconClass; + TasksThreshold = tasksThreshold; + } + + public Achievement() { } +} \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/Calendar.cs b/SMMTracker.Domain/Entities/Calendar.cs index f716ba2..1b520e6 100644 --- a/SMMTracker.Domain/Entities/Calendar.cs +++ b/SMMTracker.Domain/Entities/Calendar.cs @@ -8,6 +8,7 @@ public class Calendar : Entity public Team Team { get; private set; } = null!; public List? Events { get; set; } public List? Tasks { get; set; } + public string? UserId { get; set; } public Calendar(int teamId) { diff --git a/SMMTracker.Domain/Entities/Entity.cs b/SMMTracker.Domain/Entities/Entity.cs index ec2b97a..f83aac0 100644 --- a/SMMTracker.Domain/Entities/Entity.cs +++ b/SMMTracker.Domain/Entities/Entity.cs @@ -2,14 +2,14 @@ namespace SMMTracker.Domain.Entities; public abstract class Entity { - public int Id { get; protected set; } + public int Id { get; set; } public override bool Equals(object? obj) { return obj is Entity other && Equals(other); } - public bool Equals(Entity? other) + private bool Equals(Entity? other) { if (other is null) return false; if (Id == 0 || other.Id == 0) @@ -25,7 +25,7 @@ public override int GetHashCode() public override string ToString() { - return $"Name: {GetType().Name} Id: {Id}"; + return $"Name: {GetType().Name} Id: {Id}"; } public static bool operator ==(Entity? left, Entity? right) diff --git a/SMMTracker.Domain/Entities/Event.cs b/SMMTracker.Domain/Entities/Event.cs index 40a0f43..100198a 100644 --- a/SMMTracker.Domain/Entities/Event.cs +++ b/SMMTracker.Domain/Entities/Event.cs @@ -10,16 +10,22 @@ public class Event : Entity public string? Description { get; private set; } public DateTime Date { get; private set; } public int CalendarId { get; private set; } - + public DateTime CreatedAt { get; private set; } = DateTime.Now; + public int CreatedBy { get; private set; } public Calendar Calendar { get; private set; } = null!; public List Tasks { get; private set; } = new(); + public int TeamId { get; private set; } + public Team? Team { get; set; } = null!; - public Event(string? name, string? description, DateTime date, int calendarId) + public Event(string? name, string? description, + DateTime date, int calendarId, int createdBy, int teamId) { Name = name; Description = description; Date = date; CalendarId = calendarId; + CreatedBy = createdBy; + TeamId = teamId; } private Event() diff --git a/SMMTracker.Domain/Entities/Invitation.cs b/SMMTracker.Domain/Entities/Invitation.cs deleted file mode 100644 index 9ca12c1..0000000 --- a/SMMTracker.Domain/Entities/Invitation.cs +++ /dev/null @@ -1,15 +0,0 @@ -using SMMTracker.Domain.Enums; - -namespace SMMTracker.Domain.Entities; - -public class Invitation : Entity -{ - // в бд - public int TeamId { get; set; } - public int UserId { get; set; } - public InvitationStatus Status { get; set; } - - // связи (в бд не идут) - public Team? Team { get; set; } - public User? User { get; set; } -} \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/Task.cs b/SMMTracker.Domain/Entities/Task.cs index ea48ba7..56e1003 100644 --- a/SMMTracker.Domain/Entities/Task.cs +++ b/SMMTracker.Domain/Entities/Task.cs @@ -1,4 +1,3 @@ -using SMMTracker.Domain.Enums; using TaskStatus = SMMTracker.Domain.Enums.TaskStatus; namespace SMMTracker.Domain.Entities; @@ -9,11 +8,11 @@ public class Task : Entity public string? Description { get; private set; } public DateTime CreatedAt { get; private set; } public DateTime Deadline { get; private set; } - public TaskStatus Status { get; private set; } + public TaskStatus Status { get; set; } public int EventId { get; private set; } public int? CalendarId { get; private set; } - public Event Event { get; private set; } = null!; + public Event Event { get; set; } = null!; public Calendar? Calendar { get; private set; } public List UserTasks { get; private set; } = new(); @@ -33,37 +32,28 @@ private Task() { } - public void SetDeadline(DateTime deadline) - { - Deadline = deadline; - } - public void MoveToReview() { - if (Status == TaskStatus.InProgress) - Status = TaskStatus.Review; - throw new Exception("Cannot move task to Review from this status"); + if (Status != TaskStatus.InProgress) throw new Exception("Cannot move task to Review from this status"); + Status = TaskStatus.InReview; + return; } public void MoveToDone() { - if (Status == TaskStatus.Review) - Status = TaskStatus.Done; - throw new Exception("Cannot move task to Done from this status"); + if (Status != TaskStatus.InReview) throw new Exception("Cannot move task to Done from this status"); + Status = TaskStatus.Done; + return; } - public void ChangeTitle(string name) + public void MoveToInProgress() { - Name = name; - } - - public void ChangeDescription(string description) - { - Description = description; + if (Status != TaskStatus.InReview) + throw new Exception("Cannot move task to InProgress from this status"); + Status = TaskStatus.InProgress; } - // public void SetPrioritized(bool isPrioritized) - // { - // IsPrioritized = isPrioritized; - // } + public void ChangeName(string name) => Name = name; + public void ChangeDescription(string description) => Description = description; + public void SetDeadline(DateTime deadline) => Deadline = deadline; } \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/Team.cs b/SMMTracker.Domain/Entities/Team.cs index 88abd4d..1349f12 100644 --- a/SMMTracker.Domain/Entities/Team.cs +++ b/SMMTracker.Domain/Entities/Team.cs @@ -4,14 +4,19 @@ namespace SMMTracker.Domain.Entities; public class Team : Entity { - public string? Name { get; private set; } + public string? Name { get; set; } + public string Code { get; set; } + public Calendar? Calendar { get; set; } - public List? UserTeams { get; set; } - public List? Events { get; set; } + public List? UserTeams { get; set; } = new(); + + public string? Description { get; set; } - public Team(string name) + public Team(string name, string code, string? description = null) { Name = name; + Code = code; + Description = description; } private Team() diff --git a/SMMTracker.Domain/Entities/User.cs b/SMMTracker.Domain/Entities/User.cs index e08546d..3e976bf 100644 --- a/SMMTracker.Domain/Entities/User.cs +++ b/SMMTracker.Domain/Entities/User.cs @@ -1,38 +1,33 @@ -using System; -using System.Collections.Generic; - namespace SMMTracker.Domain.Entities; public class User : Entity { public long TelegramId { get; set; } - public string TelegramUsername { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string UserName { get; set; } - public string ProfileDescription { get; set; } + public string? ProfileDescription { get; set; } public string Hash { get; set; } + + public ICollection UserAchievements { get; set; } = new List(); - - private readonly List invitations = new(); - private readonly List userTeams = new(); - private readonly List userTasks = new(); - - public User() {} + public User() + { + } public static User Create(User otherUser) { if (string.IsNullOrWhiteSpace(otherUser.FirstName)) throw new Exception(); - if (string.IsNullOrWhiteSpace(otherUser.LastName)) - throw new Exception(); var user = new User { TelegramId = otherUser.TelegramId, FirstName = otherUser.FirstName.Trim(), LastName = otherUser.LastName.Trim(), - TelegramUsername = otherUser.UserName.Trim(), + Hash = Guid.NewGuid().ToString(), + ProfileDescription = "", + UserName = otherUser.UserName }; return user; } @@ -43,19 +38,9 @@ public void UpdateUserProfile(string firstName, string lastName, string descript throw new Exception(); if (string.IsNullOrWhiteSpace(lastName)) throw new Exception(); - + FirstName = firstName.Trim(); LastName = lastName.Trim(); ProfileDescription = description.Trim(); } - - public void AcceptInvitation(Invitation invitation) - { - - } - - public void DeclineInvitation(Invitation invitation) - { - - } } \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/UserAchievement.cs b/SMMTracker.Domain/Entities/UserAchievement.cs new file mode 100644 index 0000000..1519a08 --- /dev/null +++ b/SMMTracker.Domain/Entities/UserAchievement.cs @@ -0,0 +1,13 @@ +namespace SMMTracker.Domain.Entities; + +public class UserAchievement : Entity +{ + public int UserId { get; set; } + public User User { get; set; } + + public int AchievementId { get; set; } + public Achievement Achievement { get; set; } + + public DateTime DateReceived { get; set; } = DateTime.UtcNow; + public bool IsViewed { get; set; } = false; // Чтобы показать попап один раз +} \ No newline at end of file diff --git a/SMMTracker.Domain/Entities/UserTask.cs b/SMMTracker.Domain/Entities/UserTask.cs index 9b38103..fafc9d7 100644 --- a/SMMTracker.Domain/Entities/UserTask.cs +++ b/SMMTracker.Domain/Entities/UserTask.cs @@ -8,6 +8,6 @@ public class UserTask : Entity public int TaskId { get; set; } public UserTaskRole Role { get; set; } - public User? User { get; set; } - public Task? Task { get; set; } + public User User { get; set; } = null!; + public Task Task { get; set; } = null!; } \ No newline at end of file diff --git a/SMMTracker.Domain/Enums/InvitationStatus.cs b/SMMTracker.Domain/Enums/InvitationStatus.cs deleted file mode 100644 index b9d322a..0000000 --- a/SMMTracker.Domain/Enums/InvitationStatus.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SMMTracker.Domain.Enums; - -public enum InvitationStatus -{ - -} \ No newline at end of file diff --git a/SMMTracker.Domain/Enums/TaskStatus.cs b/SMMTracker.Domain/Enums/TaskStatus.cs index d0ac687..ffa75b3 100644 --- a/SMMTracker.Domain/Enums/TaskStatus.cs +++ b/SMMTracker.Domain/Enums/TaskStatus.cs @@ -3,6 +3,6 @@ namespace SMMTracker.Domain.Enums; public enum TaskStatus { InProgress, - Review, + InReview, Done } \ No newline at end of file diff --git a/SMMTracker.Domain/Enums/TeamRole.cs b/SMMTracker.Domain/Enums/TeamRole.cs index 8a08814..df674bb 100644 --- a/SMMTracker.Domain/Enums/TeamRole.cs +++ b/SMMTracker.Domain/Enums/TeamRole.cs @@ -2,5 +2,6 @@ namespace SMMTracker.Domain.Enums; public enum TeamRole { - + User, + Admin } \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/ICalendarRepository.cs b/SMMTracker.Domain/IRepositories/ICalendarRepository.cs new file mode 100644 index 0000000..f35635c --- /dev/null +++ b/SMMTracker.Domain/IRepositories/ICalendarRepository.cs @@ -0,0 +1,16 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface ICalendarRepository +{ + Task GetByIdAsync(int calendarId, CancellationToken cancellationToken = default); + Task GetByTeamIdAsync(int teamId, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task AddAsync(Calendar calendar, CancellationToken cancellationToken = default); + Task GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task UpdateAsync(Calendar calendar, CancellationToken cancellationToken = default); + Task DeleteAsync(int calendarId, CancellationToken cancellationToken = default); + Task ExistsAsync(int calendarId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/IEventRepository.cs b/SMMTracker.Domain/IRepositories/IEventRepository.cs new file mode 100644 index 0000000..7e44f73 --- /dev/null +++ b/SMMTracker.Domain/IRepositories/IEventRepository.cs @@ -0,0 +1,18 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface IEventRepository +{ + Task GetByIdAsync(int eventId, CancellationToken cancellationToken = default); + Task> GetEventsForCalendarAsync(int calendarId, CancellationToken cancellationToken = default); + + Task> GetEventsForMonthAsync(int calendarId, int month, int year, + CancellationToken cancellationToken = default); + + Task AddAsync(Event eventEntity, CancellationToken cancellationToken = default); + Task UpdateAsync(Event eventEntity, CancellationToken cancellationToken = default); + Task DeleteAsync(int eventId, CancellationToken cancellationToken = default); + +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/ITaskRepository.cs b/SMMTracker.Domain/IRepositories/ITaskRepository.cs new file mode 100644 index 0000000..9c8d08d --- /dev/null +++ b/SMMTracker.Domain/IRepositories/ITaskRepository.cs @@ -0,0 +1,30 @@ +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface ITaskRepository +{ + Task GetByIdAsync(int taskId, CancellationToken cancellationToken = default); + + Task GetByIdWithEventAndTeamAsync(int taskId, + CancellationToken cancellationToken = default); + + Task ExistsAsync(int taskId, CancellationToken cancellationToken = default); + Task AddAsync(SMMTracker.Domain.Entities.Task task, CancellationToken cancellationToken = default); + Task UpdateStatusToReviewAsync(SMMTracker.Domain.Entities.Task task, CancellationToken cancellationToken = default); + Task UpdateStatusToDoneAsync(SMMTracker.Domain.Entities.Task task, CancellationToken cancellationToken = default); + + Task UpdateStatusToInProgressAsync(SMMTracker.Domain.Entities.Task task, + CancellationToken cancellationToken = default); + + Task ChangeTaskNameAsync(SMMTracker.Domain.Entities.Task task, string name, + CancellationToken cancellationToken = default); + + Task ChangeTaskDescriptionAsync(SMMTracker.Domain.Entities.Task task, string name, + CancellationToken cancellationToken = default); + + Task SetTaskDeadlineAsync(SMMTracker.Domain.Entities.Task task, DateTime deadline, + CancellationToken cancellationToken = default); + + Task DeleteAsync(int taskId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/ITeamRepository.cs b/SMMTracker.Domain/IRepositories/ITeamRepository.cs new file mode 100644 index 0000000..abd175c --- /dev/null +++ b/SMMTracker.Domain/IRepositories/ITeamRepository.cs @@ -0,0 +1,16 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface ITeamRepository +{ + Task GetByIdAsync(int teamId, CancellationToken cancellationToken = default); + Task GetByCodeAsync(string code, CancellationToken cancellationToken = default); + Task ExistsAsync(int teamId, CancellationToken cancellationToken = default); + Task ExistsByCodeAsync(string code, CancellationToken cancellationToken = default); + Task AddAsync(Team team, CancellationToken cancellationToken = default); + Task UpdateAsync(Team team, CancellationToken cancellationToken = default); + Task DeleteAsync(int teamId, CancellationToken cancellationToken = default); + Task GetByIdWithMembersAsync(int teamId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/IUserRepository.cs b/SMMTracker.Domain/IRepositories/IUserRepository.cs new file mode 100644 index 0000000..f21d9ad --- /dev/null +++ b/SMMTracker.Domain/IRepositories/IUserRepository.cs @@ -0,0 +1,15 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface IUserRepository +{ + Task GetByIdAsync(int userId, CancellationToken cancellationToken = default); + Task GetByTelegramIdAsync(long telegramId, CancellationToken cancellationToken = default); + Task GetByUsernameAsync(string username, CancellationToken cancellationToken = default); + Task AddAsync(User user, CancellationToken cancellationToken = default); + Task UpdateAsync(User user, CancellationToken cancellationToken = default); + Task DeleteAsync(int userId, CancellationToken cancellationToken = default); + Task ExistsAsync(int userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/IUserTaskRepository.cs b/SMMTracker.Domain/IRepositories/IUserTaskRepository.cs new file mode 100644 index 0000000..525955b --- /dev/null +++ b/SMMTracker.Domain/IRepositories/IUserTaskRepository.cs @@ -0,0 +1,10 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface IUserTaskRepository +{ + Task AddAsync(UserTask userTask, CancellationToken cancellationToken = default); + Task DeleteAsync(int userTaskId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Domain/IRepositories/IUserTeamRepository.cs b/SMMTracker.Domain/IRepositories/IUserTeamRepository.cs new file mode 100644 index 0000000..18979f0 --- /dev/null +++ b/SMMTracker.Domain/IRepositories/IUserTeamRepository.cs @@ -0,0 +1,14 @@ +using SMMTracker.Domain.Entities; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Domain.IRepositories; + +public interface IUserTeamRepository +{ + Task GetUserTeamAsync(int teamId, int userId, CancellationToken cancellationToken = default); + Task ExistsAsync(int userTeamId, CancellationToken cancellationToken = default); + Task AddAsync(UserTeam userTeam, CancellationToken cancellationToken = default); + Task DeleteAsync(int userTeamId, CancellationToken cancellationToken = default); + Task IsUserAdminAsync(int teamId, int userid, CancellationToken cancellationToken = default); + Task> GetByUserIdAsync(int userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Data/DataContext/ApplicationDbContext.cs b/SMMTracker.Infrastructure/Data/DataContext/ApplicationDbContext.cs old mode 100644 new mode 100755 index 46c6cfe..5274c6d --- a/SMMTracker.Infrastructure/Data/DataContext/ApplicationDbContext.cs +++ b/SMMTracker.Infrastructure/Data/DataContext/ApplicationDbContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; using SMMTracker.Domain.Entities; using Task = SMMTracker.Domain.Entities.Task; -using SMMTracker.Application.Interfaces; namespace SMMTracker.Infrastructure.Data.DataContext; @@ -17,11 +17,35 @@ public ApplicationDbContext(DbContextOptions options) : ba public DbSet UserTasks { get; set; } public DbSet Teams { get; set; } public DbSet Tasks { get; set; } - public DbSet Invitations { get; set; } public DbSet Calendars { get; set; } - public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + // ДОБАВЛЕНЫ НОВЫЕ ТАБЛИЦЫ + public DbSet Achievements { get; set; } + public DbSet UserAchievements { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { - return base.SaveChangesAsync(cancellationToken); + base.OnModelCreating(modelBuilder); + + // Настройка связи "Многие ко многим" или просто корректная связь + modelBuilder.Entity() + .HasOne(ua => ua.User) + .WithMany(u => u.UserAchievements) + .HasForeignKey(ua => ua.UserId); + + modelBuilder.Entity() + .HasOne(ua => ua.Achievement) + .WithMany() + .HasForeignKey(ua => ua.AchievementId); + + // Начальные данные (Seeding) - ОБЯЗАТЕЛЬНО, чтобы ачивки существовали в БД + modelBuilder.Entity().HasData( + new Achievement("Новичок", "Вступите в свою первую команду", "bi-people", 1) { Id = 1 }, + new Achievement("Командный игрок", "Вступите в 5 команд", "bi-people-fill", 5) { Id = 2 }, + new Achievement("Трудяга", "Вступите в 10 команд", "bi-person-check", 10) { Id = 3 }, + new Achievement("Душа компании", "Вступите в 20 команд", "bi-person-hearts", 20) { Id = 4 }, + new Achievement("Легенда сообщества", "Вступите в 50 команд", "bi-megaphone", 50) { Id = 5 }, + new Achievement("Друг Пьянзиной", "Вступите в 100 команд", "bi-award", 100) { Id = 6 } + ); } } \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.cs b/SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.cs deleted file mode 100644 index 419029e..0000000 --- a/SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Data.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Teams", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Teams", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - TelegramId = table.Column(type: "INTEGER", nullable: false), - UserName = table.Column(type: "TEXT", nullable: false), - FirstName = table.Column(type: "TEXT", nullable: false), - LastName = table.Column(type: "TEXT", nullable: false), - PhotoUrl = table.Column(type: "TEXT", nullable: true), - Bio = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Calendars", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - TeamId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Calendars", x => x.Id); - table.ForeignKey( - name: "FK_Calendars_Teams_TeamId", - column: x => x.TeamId, - principalTable: "Teams", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Invitations", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - TeamId = table.Column(type: "INTEGER", nullable: false), - UserId = table.Column(type: "INTEGER", nullable: false), - Status = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Invitations", x => x.Id); - table.ForeignKey( - name: "FK_Invitations_Teams_TeamId", - column: x => x.TeamId, - principalTable: "Teams", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Invitations_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "UserTeams", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UserId = table.Column(type: "INTEGER", nullable: false), - TeamId = table.Column(type: "INTEGER", nullable: false), - Role = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserTeams", x => x.Id); - table.ForeignKey( - name: "FK_UserTeams_Teams_TeamId", - column: x => x.TeamId, - principalTable: "Teams", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_UserTeams_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Events", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false), - Status = table.Column(type: "INTEGER", nullable: false), - Description = table.Column(type: "TEXT", nullable: false), - Date = table.Column(type: "TEXT", nullable: false), - CalendarId = table.Column(type: "INTEGER", nullable: false), - TeamId = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Events", x => x.Id); - table.ForeignKey( - name: "FK_Events_Calendars_CalendarId", - column: x => x.CalendarId, - principalTable: "Calendars", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Events_Teams_TeamId", - column: x => x.TeamId, - principalTable: "Teams", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "Tasks", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false), - Description = table.Column(type: "TEXT", nullable: true), - CreatedAt = table.Column(type: "TEXT", nullable: false), - Deadline = table.Column(type: "TEXT", nullable: false), - Status = table.Column(type: "INTEGER", nullable: false), - EventId = table.Column(type: "INTEGER", nullable: false), - CalendarId = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Tasks", x => x.Id); - table.ForeignKey( - name: "FK_Tasks_Calendars_CalendarId", - column: x => x.CalendarId, - principalTable: "Calendars", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_Tasks_Events_EventId", - column: x => x.EventId, - principalTable: "Events", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "UserTasks", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UserId = table.Column(type: "INTEGER", nullable: false), - TaskId = table.Column(type: "INTEGER", nullable: false), - Role = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserTasks", x => x.Id); - table.ForeignKey( - name: "FK_UserTasks_Tasks_TaskId", - column: x => x.TaskId, - principalTable: "Tasks", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_UserTasks_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Calendars_TeamId", - table: "Calendars", - column: "TeamId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Events_CalendarId", - table: "Events", - column: "CalendarId"); - - migrationBuilder.CreateIndex( - name: "IX_Events_TeamId", - table: "Events", - column: "TeamId"); - - migrationBuilder.CreateIndex( - name: "IX_Invitations_TeamId", - table: "Invitations", - column: "TeamId"); - - migrationBuilder.CreateIndex( - name: "IX_Invitations_UserId", - table: "Invitations", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Tasks_CalendarId", - table: "Tasks", - column: "CalendarId"); - - migrationBuilder.CreateIndex( - name: "IX_Tasks_EventId", - table: "Tasks", - column: "EventId"); - - migrationBuilder.CreateIndex( - name: "IX_UserTasks_TaskId", - table: "UserTasks", - column: "TaskId"); - - migrationBuilder.CreateIndex( - name: "IX_UserTasks_UserId", - table: "UserTasks", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_UserTeams_TeamId", - table: "UserTeams", - column: "TeamId"); - - migrationBuilder.CreateIndex( - name: "IX_UserTeams_UserId", - table: "UserTeams", - column: "UserId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Invitations"); - - migrationBuilder.DropTable( - name: "UserTasks"); - - migrationBuilder.DropTable( - name: "UserTeams"); - - migrationBuilder.DropTable( - name: "Tasks"); - - migrationBuilder.DropTable( - name: "Users"); - - migrationBuilder.DropTable( - name: "Events"); - - migrationBuilder.DropTable( - name: "Calendars"); - - migrationBuilder.DropTable( - name: "Teams"); - } - } -} diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.cs b/SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.cs deleted file mode 100644 index d35a545..0000000 --- a/SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.cs +++ /dev/null @@ -1,125 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Data.Migrations -{ - /// - public partial class RemoveStatusFromEvent : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Bio", - table: "Users"); - - migrationBuilder.DropColumn( - name: "PhotoUrl", - table: "Users"); - - migrationBuilder.DropColumn( - name: "Status", - table: "Events"); - - migrationBuilder.AddColumn( - name: "ProfileDescription", - table: "Users", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "TelegramUsername", - table: "Users", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Teams", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Events", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Events", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ProfileDescription", - table: "Users"); - - migrationBuilder.DropColumn( - name: "TelegramUsername", - table: "Users"); - - migrationBuilder.AddColumn( - name: "Bio", - table: "Users", - type: "TEXT", - nullable: true); - - migrationBuilder.AddColumn( - name: "PhotoUrl", - table: "Users", - type: "TEXT", - nullable: true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Teams", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Name", - table: "Events", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Events", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "Status", - table: "Events", - type: "INTEGER", - nullable: false, - defaultValue: 0); - } - } -} diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.Designer.cs b/SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.Designer.cs similarity index 94% rename from SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.Designer.cs rename to SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.Designer.cs index 2684017..022f675 100644 --- a/SMMTracker.Infrastructure/Data/Migrations/20251201174538_RemoveStatusFromEvent.Designer.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.Designer.cs @@ -8,11 +8,11 @@ #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20251201174538_RemoveStatusFromEvent")] - partial class RemoveStatusFromEvent + [Migration("20251220170519_InitialCreate")] + partial class InitialCreate { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -46,6 +46,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CalendarId") .HasColumnType("INTEGER"); + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + b.Property("Date") .HasColumnType("TEXT"); @@ -55,7 +61,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .HasColumnType("TEXT"); - b.Property("TeamId") + b.Property("TeamId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -134,6 +140,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); @@ -161,16 +171,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("ProfileDescription") - .IsRequired() .HasColumnType("TEXT"); b.Property("TelegramId") .HasColumnType("INTEGER"); - b.Property("TelegramUsername") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("UserName") .IsRequired() .HasColumnType("TEXT"); @@ -247,11 +252,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("SMMTracker.Domain.Entities.Team", null) - .WithMany("Events") - .HasForeignKey("TeamId"); + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Calendar"); + + b.Navigation("Team"); }); modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => @@ -349,8 +358,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Navigation("Calendar"); - b.Navigation("Events"); - b.Navigation("UserTeams"); }); #pragma warning restore 612, 618 diff --git a/Data/Migrations/20251124145404_InitialCreate.cs b/SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.cs similarity index 95% rename from Data/Migrations/20251124145404_InitialCreate.cs rename to SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.cs index f50b6a1..be5fae7 100644 --- a/Data/Migrations/20251124145404_InitialCreate.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20251220170519_InitialCreate.cs @@ -3,7 +3,7 @@ #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { /// public partial class InitialCreate : Migration @@ -17,7 +17,8 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false) + Name = table.Column(type: "TEXT", nullable: true), + Code = table.Column(type: "TEXT", nullable: false) }, constraints: table => { @@ -31,11 +32,10 @@ protected override void Up(MigrationBuilder migrationBuilder) Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), TelegramId = table.Column(type: "INTEGER", nullable: false), - UserName = table.Column(type: "TEXT", nullable: false), FirstName = table.Column(type: "TEXT", nullable: false), LastName = table.Column(type: "TEXT", nullable: false), - PhotoUrl = table.Column(type: "TEXT", nullable: true), - Bio = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", nullable: false), + ProfileDescription = table.Column(type: "TEXT", nullable: true), Hash = table.Column(type: "TEXT", nullable: false) }, constraints: table => @@ -122,12 +122,13 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false), - Status = table.Column(type: "INTEGER", nullable: false), - Description = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Description = table.Column(type: "TEXT", nullable: true), Date = table.Column(type: "TEXT", nullable: false), CalendarId = table.Column(type: "INTEGER", nullable: false), - TeamId = table.Column(type: "INTEGER", nullable: true) + CreatedAt = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "INTEGER", nullable: false), + TeamId = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { @@ -142,7 +143,8 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_Events_Teams_TeamId", column: x => x.TeamId, principalTable: "Teams", - principalColumn: "Id"); + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( diff --git a/Data/Migrations/20251124145404_InitialCreate.Designer.cs b/SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.Designer.cs similarity index 72% rename from Data/Migrations/20251124145404_InitialCreate.Designer.cs rename to SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.Designer.cs index be3a431..08c41e7 100644 --- a/Data/Migrations/20251124145404_InitialCreate.Designer.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.Designer.cs @@ -1,26 +1,26 @@ // using System; -using Data.DataContext; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SMMTracker.Infrastructure.Data.DataContext; #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20251124145404_InitialCreate")] - partial class InitialCreate + [Migration("20251221002440_AddDescriptionToTeam")] + partial class AddDescriptionToTeam { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.21"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -37,7 +37,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Calendars"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -46,21 +46,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CalendarId") .HasColumnType("INTEGER"); + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + b.Property("Date") .HasColumnType("TEXT"); b.Property("Description") - .IsRequired() .HasColumnType("TEXT"); b.Property("Name") - .IsRequired() .HasColumnType("TEXT"); - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TeamId") + b.Property("TeamId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -72,7 +73,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Events"); }); - modelBuilder.Entity("Data.models.Invitation", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -96,7 +97,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Invitations"); }); - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -133,30 +134,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Code") .IsRequired() .HasColumnType("TEXT"); + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.ToTable("Teams"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Bio") - .HasColumnType("TEXT"); - b.Property("FirstName") .IsRequired() .HasColumnType("TEXT"); @@ -169,7 +173,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); - b.Property("PhotoUrl") + b.Property("ProfileDescription") .HasColumnType("TEXT"); b.Property("TelegramId") @@ -184,7 +188,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Users"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -208,7 +212,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("UserTasks"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -232,42 +236,46 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("UserTeams"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithOne("Calendar") - .HasForeignKey("Data.models.Calendar", "TeamId") + .HasForeignKey("SMMTracker.Domain.Entities.Calendar", "TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Team"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { - b.HasOne("Data.models.Calendar", "Calendar") + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Events") .HasForeignKey("CalendarId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.Team", null) - .WithMany("Events") - .HasForeignKey("TeamId"); + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Calendar"); + + b.Navigation("Team"); }); - modelBuilder.Entity("Data.models.Invitation", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany() .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Invitations") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -277,31 +285,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.HasOne("Data.models.Calendar", null) + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Tasks") .HasForeignKey("CalendarId"); - b.HasOne("Data.models.Event", "Event") + b.HasOne("SMMTracker.Domain.Entities.Event", "Event") .WithMany("Tasks") .HasForeignKey("EventId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Calendar"); + b.Navigation("Event"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { - b.HasOne("Data.models.Task", "Task") - .WithMany() + b.HasOne("SMMTracker.Domain.Entities.Task", "Task") + .WithMany("UserTasks") .HasForeignKey("TaskId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Tasks") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -311,16 +321,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany("UserTeams") .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Teams") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -330,35 +340,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Navigation("Events"); b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.Navigation("Calendar") - .IsRequired(); - - b.Navigation("Events"); - - b.Navigation("UserTeams"); + b.Navigation("UserTasks"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => { - b.Navigation("Invitations"); - - b.Navigation("Tasks"); + b.Navigation("Calendar"); - b.Navigation("Teams"); + b.Navigation("UserTeams"); }); #pragma warning restore 612, 618 } diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.cs b/SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.cs new file mode 100644 index 0000000..46b255e --- /dev/null +++ b/SMMTracker.Infrastructure/Data/Migrations/20251221002440_AddDescriptionToTeam.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SMMTracker.Infrastructure.Migrations +{ + /// + public partial class AddDescriptionToTeam : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Description", + table: "Teams", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Description", + table: "Teams"); + } + } +} diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.Designer.cs b/SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.Designer.cs similarity index 71% rename from SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.Designer.cs rename to SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.Designer.cs index 4f1ba07..f0d1a05 100644 --- a/SMMTracker.Infrastructure/Data/Migrations/20251113185337_InitialCreate.Designer.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.Designer.cs @@ -1,4 +1,5 @@ -using System; +// +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -7,19 +8,19 @@ #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20251113185337_InitialCreate")] - partial class InitialCreate + [Migration("20251222195229_AddUserIdToCalendar")] + partial class AddUserIdToCalendar { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.22"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -28,6 +29,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TeamId") .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("TeamId") @@ -36,7 +40,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Calendars"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -45,21 +49,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CalendarId") .HasColumnType("INTEGER"); + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + b.Property("Date") .HasColumnType("TEXT"); b.Property("Description") - .IsRequired() .HasColumnType("TEXT"); b.Property("Name") - .IsRequired() .HasColumnType("TEXT"); - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TeamId") + b.Property("TeamId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -71,7 +76,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Events"); }); - modelBuilder.Entity("Data.models.Invitation", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -95,7 +100,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Invitations"); }); - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -132,31 +137,38 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Code") .IsRequired() .HasColumnType("TEXT"); + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.ToTable("Teams"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Bio") + b.Property("FirstName") + .IsRequired() .HasColumnType("TEXT"); - b.Property("FirstName") + b.Property("Hash") .IsRequired() .HasColumnType("TEXT"); @@ -164,7 +176,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); - b.Property("PhotoUrl") + b.Property("ProfileDescription") .HasColumnType("TEXT"); b.Property("TelegramId") @@ -179,7 +191,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Users"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -203,7 +215,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("UserTasks"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -227,42 +239,46 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("UserTeams"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithOne("Calendar") - .HasForeignKey("Data.models.Calendar", "TeamId") + .HasForeignKey("SMMTracker.Domain.Entities.Calendar", "TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Team"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { - b.HasOne("Data.models.Calendar", "Calendar") + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Events") .HasForeignKey("CalendarId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.Team", null) - .WithMany("Events") - .HasForeignKey("TeamId"); + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("Calendar"); + + b.Navigation("Team"); }); - modelBuilder.Entity("Data.models.Invitation", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany() .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Invitations") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -272,31 +288,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.HasOne("Data.models.Calendar", null) + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Tasks") .HasForeignKey("CalendarId"); - b.HasOne("Data.models.Event", "Event") + b.HasOne("SMMTracker.Domain.Entities.Event", "Event") .WithMany("Tasks") .HasForeignKey("EventId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Calendar"); + b.Navigation("Event"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { - b.HasOne("Data.models.Task", "Task") - .WithMany() + b.HasOne("SMMTracker.Domain.Entities.Task", "Task") + .WithMany("UserTasks") .HasForeignKey("TaskId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Tasks") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -306,16 +324,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany("UserTeams") .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Teams") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -325,35 +343,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Navigation("Events"); b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.Navigation("Calendar") - .IsRequired(); - - b.Navigation("Events"); - - b.Navigation("UserTeams"); + b.Navigation("UserTasks"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => { - b.Navigation("Invitations"); - - b.Navigation("Tasks"); + b.Navigation("Calendar"); - b.Navigation("Teams"); + b.Navigation("UserTeams"); }); #pragma warning restore 612, 618 } diff --git a/SMMTracker.Infrastructure/Data/Migrations/20251114193602_AddUserHash.cs b/SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.cs similarity index 62% rename from SMMTracker.Infrastructure/Data/Migrations/20251114193602_AddUserHash.cs rename to SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.cs index 7a4e350..7b8e3f2 100644 --- a/SMMTracker.Infrastructure/Data/Migrations/20251114193602_AddUserHash.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20251222195229_AddUserIdToCalendar.cs @@ -2,28 +2,27 @@ #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { /// - public partial class AddUserHash : Migration + public partial class AddUserIdToCalendar : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "Hash", - table: "Users", + name: "UserId", + table: "Calendars", type: "TEXT", - nullable: false, - defaultValue: ""); + nullable: true); } /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "Hash", - table: "Users"); + name: "UserId", + table: "Calendars"); } } } diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.Designer.cs similarity index 55% rename from Data/Migrations/ApplicationDbContextModelSnapshot.cs rename to SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.Designer.cs index 05b7ab8..3fb20d6 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.Designer.cs @@ -1,23 +1,102 @@ // using System; -using Data.DataContext; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SMMTracker.Infrastructure.Data.DataContext; #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot + [Migration("20260105174215_AddAchievementsFinal")] + partial class AddAchievementsFinal { - protected override void BuildModel(ModelBuilder modelBuilder) + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.21"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Achievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IconClass") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TasksThreshold") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Achievements"); + + b.HasData( + new + { + Id = 1, + Description = "Выполните свою первую задачу", + IconClass = "bi-check-circle", + TasksThreshold = 1, + Title = "Первые шаги" + }, + new + { + Id = 2, + Description = "Выполните 10 задач", + IconClass = "bi-star", + TasksThreshold = 10, + Title = "На опыте" + }, + new + { + Id = 3, + Description = "Выполните 50 задач", + IconClass = "bi-gem", + TasksThreshold = 50, + Title = "Трудяга" + }, + new + { + Id = 4, + Description = "Выполните 100 задач", + IconClass = "bi-trophy", + TasksThreshold = 100, + Title = "Мастер" + }, + new + { + Id = 5, + Description = "Выполните 200 задач", + IconClass = "bi-crown", + TasksThreshold = 200, + Title = "Элита" + }, + new + { + Id = 6, + Description = "Выполните 300 задач", + IconClass = "bi-rocket-takeoff", + TasksThreshold = 300, + Title = "Легенда" + }); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -26,6 +105,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TeamId") .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("TeamId") @@ -34,7 +116,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Calendars"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -43,21 +125,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CalendarId") .HasColumnType("INTEGER"); + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + b.Property("Date") .HasColumnType("TEXT"); b.Property("Description") - .IsRequired() .HasColumnType("TEXT"); b.Property("Name") - .IsRequired() .HasColumnType("TEXT"); - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TeamId") + b.Property("TeamId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -69,31 +152,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Events"); }); - modelBuilder.Entity("Data.models.Invitation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TeamId") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TeamId"); - - b.HasIndex("UserId"); - - b.ToTable("Invitations"); - }); - - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -130,30 +189,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Code") .IsRequired() .HasColumnType("TEXT"); + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.ToTable("Teams"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Bio") - .HasColumnType("TEXT"); - b.Property("FirstName") .IsRequired() .HasColumnType("TEXT"); @@ -166,7 +228,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); - b.Property("PhotoUrl") + b.Property("ProfileDescription") .HasColumnType("TEXT"); b.Property("TelegramId") @@ -181,7 +243,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Users"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AchievementId") + .HasColumnType("INTEGER"); + + b.Property("DateReceived") + .HasColumnType("TEXT"); + + b.Property("IsViewed") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AchievementId"); + + b.HasIndex("UserId"); + + b.ToTable("UserAchievements"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -205,7 +294,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("UserTasks"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -229,76 +318,82 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("UserTeams"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithOne("Calendar") - .HasForeignKey("Data.models.Calendar", "TeamId") + .HasForeignKey("SMMTracker.Domain.Entities.Calendar", "TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Team"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { - b.HasOne("Data.models.Calendar", "Calendar") + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Events") .HasForeignKey("CalendarId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.Team", null) - .WithMany("Events") - .HasForeignKey("TeamId"); - - b.Navigation("Calendar"); - }); - - modelBuilder.Entity("Data.models.Invitation", b => - { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany() .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Invitations") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Navigation("Calendar"); b.Navigation("Team"); - - b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Task", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.HasOne("Data.models.Calendar", null) + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") .WithMany("Tasks") .HasForeignKey("CalendarId"); - b.HasOne("Data.models.Event", "Event") + b.HasOne("SMMTracker.Domain.Entities.Event", "Event") .WithMany("Tasks") .HasForeignKey("EventId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Calendar"); + b.Navigation("Event"); }); - modelBuilder.Entity("Data.models.UserTask", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => { - b.HasOne("Data.models.Task", "Task") + b.HasOne("SMMTracker.Domain.Entities.Achievement", "Achievement") .WithMany() + .HasForeignKey("AchievementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany("UserAchievements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Achievement"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => + { + b.HasOne("SMMTracker.Domain.Entities.Task", "Task") + .WithMany("UserTasks") .HasForeignKey("TaskId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Tasks") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -308,16 +403,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.UserTeam", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => { - b.HasOne("Data.models.Team", "Team") + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany("UserTeams") .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Data.models.User", "User") - .WithMany("Teams") + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -327,35 +422,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Data.models.Calendar", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Navigation("Events"); b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Event", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => { b.Navigation("Tasks"); }); - modelBuilder.Entity("Data.models.Team", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { - b.Navigation("Calendar") - .IsRequired(); + b.Navigation("UserTasks"); + }); - b.Navigation("Events"); + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => + { + b.Navigation("Calendar"); b.Navigation("UserTeams"); }); - modelBuilder.Entity("Data.models.User", b => + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => { - b.Navigation("Invitations"); - - b.Navigation("Tasks"); - - b.Navigation("Teams"); + b.Navigation("UserAchievements"); }); #pragma warning restore 612, 618 } diff --git a/SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.cs b/SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.cs new file mode 100644 index 0000000..ca46cae --- /dev/null +++ b/SMMTracker.Infrastructure/Data/Migrations/20260105174215_AddAchievementsFinal.cs @@ -0,0 +1,150 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace SMMTracker.Infrastructure.Migrations +{ + /// + public partial class AddAchievementsFinal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Invitations"); + + migrationBuilder.AlterColumn( + name: "CreatedBy", + table: "Events", + type: "INTEGER", + nullable: false, + oldClrType: typeof(string), + oldType: "TEXT"); + + migrationBuilder.CreateTable( + name: "Achievements", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + IconClass = table.Column(type: "TEXT", nullable: false), + TasksThreshold = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Achievements", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserAchievements", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "INTEGER", nullable: false), + AchievementId = table.Column(type: "INTEGER", nullable: false), + DateReceived = table.Column(type: "TEXT", nullable: false), + IsViewed = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserAchievements", x => x.Id); + table.ForeignKey( + name: "FK_UserAchievements_Achievements_AchievementId", + column: x => x.AchievementId, + principalTable: "Achievements", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserAchievements_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "Achievements", + columns: new[] { "Id", "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[,] + { + { 1, "Выполните свою первую задачу", "bi-check-circle", 1, "Первые шаги" }, + { 2, "Выполните 10 задач", "bi-star", 10, "На опыте" }, + { 3, "Выполните 50 задач", "bi-gem", 50, "Трудяга" }, + { 4, "Выполните 100 задач", "bi-trophy", 100, "Мастер" }, + { 5, "Выполните 200 задач", "bi-crown", 200, "Элита" }, + { 6, "Выполните 300 задач", "bi-rocket-takeoff", 300, "Легенда" } + }); + + migrationBuilder.CreateIndex( + name: "IX_UserAchievements_AchievementId", + table: "UserAchievements", + column: "AchievementId"); + + migrationBuilder.CreateIndex( + name: "IX_UserAchievements_UserId", + table: "UserAchievements", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserAchievements"); + + migrationBuilder.DropTable( + name: "Achievements"); + + migrationBuilder.AlterColumn( + name: "CreatedBy", + table: "Events", + type: "TEXT", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.CreateTable( + name: "Invitations", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TeamId = table.Column(type: "INTEGER", nullable: false), + UserId = table.Column(type: "INTEGER", nullable: false), + Status = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Invitations", x => x.Id); + table.ForeignKey( + name: "FK_Invitations_Teams_TeamId", + column: x => x.TeamId, + principalTable: "Teams", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Invitations_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Invitations_TeamId", + table: "Invitations", + column: "TeamId"); + + migrationBuilder.CreateIndex( + name: "IX_Invitations_UserId", + table: "Invitations", + column: "UserId"); + } + } +} diff --git a/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.Designer.cs b/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.Designer.cs new file mode 100644 index 0000000..597de06 --- /dev/null +++ b/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.Designer.cs @@ -0,0 +1,456 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SMMTracker.Infrastructure.Data.DataContext; + +#nullable disable + +namespace SMMTracker.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260105181629_UpdateAchievementsToTeams")] + partial class UpdateAchievementsToTeams + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Achievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IconClass") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TasksThreshold") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Achievements"); + + b.HasData( + new + { + Id = 1, + Description = "Вступите в свою первую команду", + IconClass = "bi-people", + TasksThreshold = 1, + Title = "Новичок" + }, + new + { + Id = 2, + Description = "Вступите в 5 команд", + IconClass = "bi-people-fill", + TasksThreshold = 5, + Title = "Командный игрок" + }, + new + { + Id = 3, + Description = "Вступите в 10 команд", + IconClass = "bi-person-check", + TasksThreshold = 10, + Title = "Трудяга" + }, + new + { + Id = 4, + Description = "Вступите в 20 команд", + IconClass = "bi-person-hearts", + TasksThreshold = 20, + Title = "Душа компании" + }, + new + { + Id = 5, + Description = "Вступите в 50 команд", + IconClass = "bi-megaphone", + TasksThreshold = 50, + Title = "Легенда сообщества" + }, + new + { + Id = 6, + Description = "Вступите в 100 команд", + IconClass = "bi-award", + TasksThreshold = 100, + Title = "Друг Пьянзиной" + }); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TeamId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TeamId") + .IsUnique(); + + b.ToTable("Calendars"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CalendarId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("TeamId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CalendarId"); + + b.HasIndex("TeamId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CalendarId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Deadline") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CalendarId"); + + b.HasIndex("EventId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Teams"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileDescription") + .HasColumnType("TEXT"); + + b.Property("TelegramId") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AchievementId") + .HasColumnType("INTEGER"); + + b.Property("DateReceived") + .HasColumnType("TEXT"); + + b.Property("IsViewed") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AchievementId"); + + b.HasIndex("UserId"); + + b.ToTable("UserAchievements"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("TaskId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.HasIndex("UserId"); + + b.ToTable("UserTasks"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("TeamId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.HasIndex("UserId"); + + b.ToTable("UserTeams"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => + { + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithOne("Calendar") + .HasForeignKey("SMMTracker.Domain.Entities.Calendar", "TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => + { + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") + .WithMany("Events") + .HasForeignKey("CalendarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Calendar"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => + { + b.HasOne("SMMTracker.Domain.Entities.Calendar", "Calendar") + .WithMany("Tasks") + .HasForeignKey("CalendarId"); + + b.HasOne("SMMTracker.Domain.Entities.Event", "Event") + .WithMany("Tasks") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Calendar"); + + b.Navigation("Event"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => + { + b.HasOne("SMMTracker.Domain.Entities.Achievement", "Achievement") + .WithMany() + .HasForeignKey("AchievementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany("UserAchievements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Achievement"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => + { + b.HasOne("SMMTracker.Domain.Entities.Task", "Task") + .WithMany("UserTasks") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTeam", b => + { + b.HasOne("SMMTracker.Domain.Entities.Team", "Team") + .WithMany("UserTeams") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => + { + b.Navigation("Events"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Event", b => + { + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => + { + b.Navigation("UserTasks"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.Team", b => + { + b.Navigation("Calendar"); + + b.Navigation("UserTeams"); + }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => + { + b.Navigation("UserAchievements"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.cs b/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.cs new file mode 100644 index 0000000..49b16f8 --- /dev/null +++ b/SMMTracker.Infrastructure/Data/Migrations/20260105181629_UpdateAchievementsToTeams.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SMMTracker.Infrastructure.Migrations +{ + /// + public partial class UpdateAchievementsToTeams : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 1, + columns: new[] { "Description", "IconClass", "Title" }, + values: new object[] { "Вступите в свою первую команду", "bi-people", "Новичок" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 2, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Вступите в 5 команд", "bi-people-fill", 5, "Командный игрок" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 3, + columns: new[] { "Description", "IconClass", "TasksThreshold" }, + values: new object[] { "Вступите в 10 команд", "bi-person-check", 10 }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 4, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Вступите в 20 команд", "bi-person-hearts", 20, "Душа компании" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 5, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Вступите в 50 команд", "bi-megaphone", 50, "Легенда сообщества" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 6, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Вступите в 100 команд", "bi-award", 100, "Друг Пьянзиной" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 1, + columns: new[] { "Description", "IconClass", "Title" }, + values: new object[] { "Выполните свою первую задачу", "bi-check-circle", "Первые шаги" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 2, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Выполните 10 задач", "bi-star", 10, "На опыте" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 3, + columns: new[] { "Description", "IconClass", "TasksThreshold" }, + values: new object[] { "Выполните 50 задач", "bi-gem", 50 }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 4, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Выполните 100 задач", "bi-trophy", 100, "Мастер" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 5, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Выполните 200 задач", "bi-crown", 200, "Элита" }); + + migrationBuilder.UpdateData( + table: "Achievements", + keyColumn: "Id", + keyValue: 6, + columns: new[] { "Description", "IconClass", "TasksThreshold", "Title" }, + values: new object[] { "Выполните 300 задач", "bi-rocket-takeoff", 300, "Легенда" }); + } + } +} diff --git a/SMMTracker.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SMMTracker.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs index a4c6a58..1d91bd2 100644 --- a/SMMTracker.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SMMTracker.Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -7,7 +7,7 @@ #nullable disable -namespace Data.Migrations +namespace SMMTracker.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] partial class ApplicationDbContextModelSnapshot : ModelSnapshot @@ -17,6 +17,82 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + modelBuilder.Entity("SMMTracker.Domain.Entities.Achievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IconClass") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TasksThreshold") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Achievements"); + + b.HasData( + new + { + Id = 1, + Description = "Вступите в свою первую команду", + IconClass = "bi-people", + TasksThreshold = 1, + Title = "Новичок" + }, + new + { + Id = 2, + Description = "Вступите в 5 команд", + IconClass = "bi-people-fill", + TasksThreshold = 5, + Title = "Командный игрок" + }, + new + { + Id = 3, + Description = "Вступите в 10 команд", + IconClass = "bi-person-check", + TasksThreshold = 10, + Title = "Трудяга" + }, + new + { + Id = 4, + Description = "Вступите в 20 команд", + IconClass = "bi-person-hearts", + TasksThreshold = 20, + Title = "Душа компании" + }, + new + { + Id = 5, + Description = "Вступите в 50 команд", + IconClass = "bi-megaphone", + TasksThreshold = 50, + Title = "Легенда сообщества" + }, + new + { + Id = 6, + Description = "Вступите в 100 команд", + IconClass = "bi-award", + TasksThreshold = 100, + Title = "Друг Пьянзиной" + }); + }); + modelBuilder.Entity("SMMTracker.Domain.Entities.Calendar", b => { b.Property("Id") @@ -26,6 +102,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TeamId") .HasColumnType("INTEGER"); + b.Property("UserId") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("TeamId") @@ -43,6 +122,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CalendarId") .HasColumnType("INTEGER"); + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("INTEGER"); + b.Property("Date") .HasColumnType("TEXT"); @@ -52,7 +137,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .HasColumnType("TEXT"); - b.Property("TeamId") + b.Property("TeamId") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -64,30 +149,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Events"); }); - modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TeamId") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("TeamId"); - - b.HasIndex("UserId"); - - b.ToTable("Invitations"); - }); - modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => { b.Property("Id") @@ -131,6 +192,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); @@ -158,16 +226,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("ProfileDescription") - .IsRequired() .HasColumnType("TEXT"); b.Property("TelegramId") .HasColumnType("INTEGER"); - b.Property("TelegramUsername") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("UserName") .IsRequired() .HasColumnType("TEXT"); @@ -177,6 +240,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Users"); }); + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AchievementId") + .HasColumnType("INTEGER"); + + b.Property("DateReceived") + .HasColumnType("TEXT"); + + b.Property("IsViewed") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AchievementId"); + + b.HasIndex("UserId"); + + b.ToTable("UserAchievements"); + }); + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { b.Property("Id") @@ -244,30 +334,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("SMMTracker.Domain.Entities.Team", null) - .WithMany("Events") - .HasForeignKey("TeamId"); - - b.Navigation("Calendar"); - }); - - modelBuilder.Entity("SMMTracker.Domain.Entities.Invitation", b => - { b.HasOne("SMMTracker.Domain.Entities.Team", "Team") .WithMany() .HasForeignKey("TeamId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("SMMTracker.Domain.Entities.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Navigation("Calendar"); b.Navigation("Team"); - - b.Navigation("User"); }); modelBuilder.Entity("SMMTracker.Domain.Entities.Task", b => @@ -287,6 +362,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Event"); }); + modelBuilder.Entity("SMMTracker.Domain.Entities.UserAchievement", b => + { + b.HasOne("SMMTracker.Domain.Entities.Achievement", "Achievement") + .WithMany() + .HasForeignKey("AchievementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SMMTracker.Domain.Entities.User", "User") + .WithMany("UserAchievements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Achievement"); + + b.Navigation("User"); + }); + modelBuilder.Entity("SMMTracker.Domain.Entities.UserTask", b => { b.HasOne("SMMTracker.Domain.Entities.Task", "Task") @@ -346,10 +440,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Calendar"); - b.Navigation("Events"); - b.Navigation("UserTeams"); }); + + modelBuilder.Entity("SMMTracker.Domain.Entities.User", b => + { + b.Navigation("UserAchievements"); + }); #pragma warning restore 612, 618 } } diff --git a/SMMTracker.Infrastructure/Repositories/CalendarRepository.cs b/SMMTracker.Infrastructure/Repositories/CalendarRepository.cs new file mode 100644 index 0000000..09a75c6 --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/CalendarRepository.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class CalendarRepository : ICalendarRepository +{ + private readonly IApplicationDbContext _context; + + public CalendarRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(int calendarId, CancellationToken cancellationToken = default) + { + return await _context.Calendars + .Include(c => c.Team) + .Include(c => c.Events) + .Include(c => c.Tasks) + .FirstOrDefaultAsync(c => c.Id == calendarId, cancellationToken: cancellationToken); + } + + public async Task GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await _context.Calendars + .FirstOrDefaultAsync(c => c.UserId == userId, cancellationToken); + } + + public async Task GetByTeamIdAsync(int teamId, CancellationToken cancellationToken = default) + { + return await _context.Calendars + .Include(c => c.Team) + .Include(c => c.Events) + .Include(c => c.Tasks) + .FirstOrDefaultAsync(c => c.TeamId == teamId, cancellationToken: cancellationToken); + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _context.Calendars + .Include(c => c.Team) + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task AddAsync(Calendar calendar, CancellationToken cancellationToken = default) + { + await _context.Calendars.AddAsync(calendar, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateAsync(Calendar calendar, CancellationToken cancellationToken = default) + { + _context.Calendars.Update(calendar); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int calendarId, CancellationToken cancellationToken = default) + { + var calendar = await _context.Calendars.FindAsync(calendarId); + if (calendar != null) + { + _context.Calendars.Remove(calendar); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task ExistsAsync(int calendarId, CancellationToken cancellationToken = default) + { + return await _context.Calendars.AnyAsync(c => c.Id == calendarId, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/EventRepository.cs b/SMMTracker.Infrastructure/Repositories/EventRepository.cs new file mode 100644 index 0000000..1cc0b2f --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/EventRepository.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class EventRepository : IEventRepository +{ + private readonly IApplicationDbContext _context; + + public EventRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(int eventId, CancellationToken cancellationToken = default) + { + return await _context.Events + .Include(e => e.Tasks) + .Include(e => e.Calendar) + .Include(e => e.Team) + .FirstOrDefaultAsync(e => e.Id == eventId, cancellationToken: cancellationToken); + } + + public async Task> GetEventsForCalendarAsync(int calendarId, + CancellationToken cancellationToken = default) + { + return await _context.Events + .Where(e => e.CalendarId == calendarId) + .Include(e => e.Tasks) + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task> GetEventsForMonthAsync(int calendarId, int month, int year, + CancellationToken cancellationToken = default) + { + return await _context.Events + .Where(e => e.CalendarId == calendarId && e.Date.Year == year && e.Date.Month == month) + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task> GetEventsForTeamAsync(int teamId, CancellationToken cancellationToken = default) + { + return await _context.Events + .Where(e => e.Calendar.TeamId == teamId) + .Include(e => e.Tasks) + .Include(e => e.Calendar) + .AsSplitQuery() + .ToListAsync(cancellationToken: cancellationToken); + } + + public async Task AddAsync(Event eventEntity, CancellationToken cancellationToken = default) + { + await _context.Events.AddAsync(eventEntity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateAsync(Event eventEntity, CancellationToken cancellationToken = default) + { + _context.Events.Update(eventEntity); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int eventId, CancellationToken cancellationToken = default) + { + var eventEntity = await _context.Events.FindAsync(eventId); + if (eventEntity != null) + { + _context.Events.Remove(eventEntity); + await _context.SaveChangesAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/TaskRepository.cs b/SMMTracker.Infrastructure/Repositories/TaskRepository.cs new file mode 100644 index 0000000..a64eab5 --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/TaskRepository.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class TaskRepository : ITaskRepository +{ + private readonly IApplicationDbContext _context; + + public TaskRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(int taskId, + CancellationToken cancellationToken = default) + { + return await _context.Tasks.FirstOrDefaultAsync(t => t.Id == taskId, cancellationToken: cancellationToken); + } + + public async Task GetByIdWithEventAndTeamAsync(int taskId, + CancellationToken cancellationToken = default) + { + return await _context.Tasks + .Include(t => t.Event) + .ThenInclude(e => e.Team) + .FirstOrDefaultAsync(t => t.Id == taskId, cancellationToken: cancellationToken); + } + + public async Task ExistsAsync(int taskId, CancellationToken cancellationToken = default) + { + return await _context.Tasks.AnyAsync(t => t.Id == taskId, cancellationToken: cancellationToken); + } + + public async Task AddAsync(Domain.Entities.Task task, CancellationToken cancellationToken = default) + { + await _context.Tasks.AddAsync(task, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateStatusToReviewAsync(Domain.Entities.Task task, + CancellationToken cancellationToken = default) + { + task.MoveToReview(); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateStatusToDoneAsync(Domain.Entities.Task task, CancellationToken cancellationToken = default) + { + task.MoveToDone(); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateStatusToInProgressAsync(Domain.Entities.Task task, + CancellationToken cancellationToken = default) + { + task.MoveToInProgress(); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task ChangeTaskNameAsync(Domain.Entities.Task task, string name, + CancellationToken cancellationToken = default) + { + task.ChangeName(name); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task ChangeTaskDescriptionAsync(Domain.Entities.Task task, string description, + CancellationToken cancellationToken = default) + { + task.ChangeDescription(description); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task SetTaskDeadlineAsync(Domain.Entities.Task task, DateTime deadline, + CancellationToken cancellationToken = default) + { + task.SetDeadline(deadline); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int taskId, CancellationToken cancellationToken = default) + { + var task = await _context.Tasks.FindAsync(taskId); + if (task != null) + { + _context.Tasks.Remove(task); + await _context.SaveChangesAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/TeamRepository.cs b/SMMTracker.Infrastructure/Repositories/TeamRepository.cs new file mode 100644 index 0000000..46302cd --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/TeamRepository.cs @@ -0,0 +1,72 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class TeamRepository : ITeamRepository +{ + private IApplicationDbContext _context; + + public TeamRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + return await _context.Teams + .Include(t => t.UserTeams) + .ThenInclude(ut => ut.User) + .FirstOrDefaultAsync(t => t.Id == id, cancellationToken: cancellationToken); + } + + public async Task GetByCodeAsync(string code, CancellationToken cancellationToken = default) + { + return await _context.Teams + .AsNoTracking() + .FirstOrDefaultAsync(team => team.Code == code, cancellationToken: cancellationToken); + } + + public async Task ExistsAsync(int teamId, CancellationToken cancellationToken = default) + { + return await _context.Teams.AnyAsync(team => team.Id == teamId, cancellationToken: cancellationToken); + } + + public async Task ExistsByCodeAsync(string code, CancellationToken cancellationToken = default) + { + return await _context.Teams.AnyAsync(team => team.Code == code, cancellationToken: cancellationToken); + } + + public async Task AddAsync(Team team, CancellationToken cancellationToken = default) + { + await _context.Teams.AddAsync(team, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateAsync(Team team, CancellationToken cancellationToken = default) + { + _context.Teams.Update(team); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int teamId, CancellationToken cancellationToken = default) + { + var team = await _context.Teams.FindAsync(teamId); + if (team != null) + { + _context.Teams.Remove(team); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task GetByIdWithMembersAsync(int teamId, CancellationToken cancellationToken = default) + { + return await _context.Teams + .Include(t => t.UserTeams) + .ThenInclude(ut => ut.User) + .FirstOrDefaultAsync(t => t.Id == teamId, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/UserRepository.cs b/SMMTracker.Infrastructure/Repositories/UserRepository.cs index ec7583a..b1f0e10 100644 --- a/SMMTracker.Infrastructure/Repositories/UserRepository.cs +++ b/SMMTracker.Infrastructure/Repositories/UserRepository.cs @@ -1,35 +1,61 @@ -using Microsoft.EntityFrameworkCore; -using SMMTracker.Application.Interfaces.Repositories; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; using SMMTracker.Domain.Entities; -using SMMTracker.Infrastructure.Data.DataContext; +using SMMTracker.Domain.IRepositories; using Task = System.Threading.Tasks.Task; namespace SMMTracker.Infrastructure.Repositories; public class UserRepository : IUserRepository { - private readonly ApplicationDbContext _context; + private readonly IApplicationDbContext _context; - public UserRepository(ApplicationDbContext context) + public UserRepository(IApplicationDbContext context) { _context = context; } - public async Task GetByTelegramIdAsync(long telegramId) + public async Task GetByIdAsync(int userId, CancellationToken cancellationToken = default) + { + return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken: cancellationToken); + } + + public async Task GetByTelegramIdAsync(long telegramId, CancellationToken cancellationToken = default) + { + return await _context.Users.FirstOrDefaultAsync(user => user.TelegramId == telegramId, + cancellationToken: cancellationToken); + } + + public async Task GetByUsernameAsync(string username, CancellationToken cancellationToken = default) { return await _context.Users - .FirstOrDefaultAsync(x => x.TelegramId == telegramId); + .FirstOrDefaultAsync(u => u.UserName.ToLower() == username.ToLower(), cancellationToken: cancellationToken); } - public async Task AddAsync(User user) + public async Task AddAsync(User user, CancellationToken cancellationToken = default) { - await _context.Users.AddAsync(user); - await _context.SaveChangesAsync(); + await _context.Users.AddAsync(user, cancellationToken); + await _context.SaveChangesAsync(default); } - public async Task UpdateAsync(User user) + public async Task UpdateAsync(User user, CancellationToken cancellationToken = default) { _context.Users.Update(user); - await _context.SaveChangesAsync(); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int userId, CancellationToken cancellationToken = default) + { + var user = await _context.Users.FindAsync(userId); + if (user != null) + { + _context.Users.Remove(user); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task ExistsAsync(int userId, CancellationToken cancellationToken = default) + { + return await _context.Users.AnyAsync(u => u.Id == userId, cancellationToken: cancellationToken); } } \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/UserTaskRepository.cs b/SMMTracker.Infrastructure/Repositories/UserTaskRepository.cs new file mode 100644 index 0000000..1ad2177 --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/UserTaskRepository.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class UserTaskRepository : IUserTaskRepository +{ + private readonly IApplicationDbContext _context; + + public UserTaskRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetUserTaskAsync(int taskId, int userId, CancellationToken cancellationToken = default) + { + return await _context.UserTasks.FirstOrDefaultAsync(ut => ut.TaskId == taskId && ut.UserId == userId, + cancellationToken: cancellationToken); + } + + public async Task AddAsync(UserTask userTask, CancellationToken cancellationToken = default) + { + await _context.UserTasks.AddAsync(userTask, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int userTaskId, CancellationToken cancellationToken = default) + { + var userTask = await _context.UserTasks.FindAsync(userTaskId); + if (userTask != null) + { + _context.UserTasks.Remove(userTask); + await _context.SaveChangesAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Repositories/UserTeamRepository.cs b/SMMTracker.Infrastructure/Repositories/UserTeamRepository.cs new file mode 100644 index 0000000..259f391 --- /dev/null +++ b/SMMTracker.Infrastructure/Repositories/UserTeamRepository.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.Enums; +using SMMTracker.Domain.IRepositories; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Infrastructure.Repositories; + +public class UserTeamRepository : IUserTeamRepository +{ + private readonly IApplicationDbContext _context; + + public UserTeamRepository(IApplicationDbContext context) + { + _context = context; + } + + public async Task GetUserTeamAsync(int teamId, int userId, CancellationToken cancellationToken = default) + { + return await _context.UserTeams.FirstOrDefaultAsync(ut => ut.TeamId == teamId && ut.UserId == userId, + cancellationToken: cancellationToken); + } + + public async Task ExistsAsync(int userTeamId, CancellationToken cancellationToken = default) + { + return await _context.UserTeams.AnyAsync(userTeam => userTeam.Id == userTeamId, + cancellationToken: cancellationToken); + } + + public async Task AddAsync(UserTeam userTeam, CancellationToken cancellationToken = default) + { + await _context.UserTeams.AddAsync(userTeam, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int userTeamId, CancellationToken cancellationToken = default) + { + var userTeam = await _context.UserTeams.FindAsync(userTeamId); + if (userTeam != null) + { + _context.UserTeams.Remove(userTeam); + await _context.SaveChangesAsync(cancellationToken); + } + } + + public async Task IsUserAdminAsync(int teamId, int userId, CancellationToken cancellationToken = default) + { + return await _context.UserTeams + .AnyAsync(ut => ut.TeamId == teamId && ut.UserId == userId && ut.Role == TeamRole.Admin, + cancellationToken: cancellationToken); + } + + public async Task> GetByUserIdAsync(int userId, CancellationToken cancellationToken = default) + { + return await _context.UserTeams + .Where(ut => ut.UserId == userId) + .Include(ut => ut.Team) + .ToListAsync(cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Services/UserManager.cs b/SMMTracker.Infrastructure/Services/UserManager.cs deleted file mode 100644 index c733064..0000000 --- a/SMMTracker.Infrastructure/Services/UserManager.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; -using SMMTracker.Domain.Entities; -using SMMTracker.Infrastructure.Data.DataContext; - -namespace SMMTracker.Infrastructure.Services; - -public class UserManager : IUserManager -{ - private readonly ApplicationDbContext _context; - - public UserManager(ApplicationDbContext context) - { - _context = context; - } - - public async Task FindOrCreateUserAsync(User otherUser) - { - var user = await _context.Users - .FirstOrDefaultAsync(x => x.TelegramId == otherUser.TelegramId); - - if (user == null) - { - user = User.Create(user); - await _context.Users.AddAsync(user); - await _context.SaveChangesAsync(); - } - - return new UserDto - { - Id = user.Id, - TelegramId = user.TelegramId, - FirstName = user.FirstName, - LastName = user.LastName, - Username = user.TelegramUsername - }; - } -} \ No newline at end of file diff --git a/SMMTracker.Infrastructure/Services/UserStateService.cs b/SMMTracker.Infrastructure/Services/UserStateService.cs deleted file mode 100644 index 02ce2ce..0000000 --- a/SMMTracker.Infrastructure/Services/UserStateService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SMMTracker.Application.Dtos; - -namespace SMMTracker.Infrastructure.Services; - -public class UserStateService -{ - public UserDto CurrentUser { get; private set; } - - public event Action OnChange; - - public void SetUser(UserDto user) - { - CurrentUser = user; - OnChange?.Invoke(); - } - - public void LogOut() - { - CurrentUser = null; - OnChange?.Invoke(); - } -} \ No newline at end of file diff --git a/SMMTracker.Tests/CalendareServiceTests.cs b/SMMTracker.Tests/CalendareServiceTests.cs new file mode 100644 index 0000000..9534763 --- /dev/null +++ b/SMMTracker.Tests/CalendareServiceTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; +using Moq; +using FluentAssertions; +using SMMTracker.Application.Dtos; +using SMMTracker.Application.Services; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.IRepositories; +using Xunit; + +namespace SMMTracker.Tests; + +public class CalendarServiceTests +{ + private readonly Mock _calendarRepoMock; + private readonly Mock _teamRepoMock; + private readonly CalendarService _service; + + public CalendarServiceTests() + { + _calendarRepoMock = new Mock(); + _teamRepoMock = new Mock(); + _service = new CalendarService(_calendarRepoMock.Object, _teamRepoMock.Object); + } + + [Fact] + public async void CreateCalendarAsync_ShouldCreateCalendar_WhenTeamExists() + { + var teamId = 1; + var dto = new CreateCalendarDto { TeamId = teamId }; + _teamRepoMock.Setup(r => r.ExistsAsync(teamId, It.IsAny())) + .ReturnsAsync(true); + + await _service.CreateCalendarAsync(dto); + _calendarRepoMock.Verify(r => + r.AddAsync(It.Is(c => c.TeamId == teamId), It.IsAny()), + Times.Once); + } + + [Fact] + public async void CreateCalendarAsync_ShouldThrowException_WhenTeamDoesNotExist() + { + var teamId = 999; + var dto = new CreateCalendarDto { TeamId = teamId }; + + _teamRepoMock.Setup(r => + r.ExistsAsync(teamId, It.IsAny())) + .ReturnsAsync(false); + var action = async () => await _service.CreateCalendarAsync(dto); + + await action.Should().ThrowAsync() + .WithMessage($"Команда с Id={teamId} не найдена."); + _calendarRepoMock.Verify(r => + r.AddAsync(It.IsAny(), It.IsAny()), + Times.Never); + } +} \ No newline at end of file diff --git a/SMMTracker.Tests/SMMTracker.Tests.csproj b/SMMTracker.Tests/SMMTracker.Tests.csproj new file mode 100644 index 0000000..2fd800c --- /dev/null +++ b/SMMTracker.Tests/SMMTracker.Tests.csproj @@ -0,0 +1,28 @@ + + + net8.0 + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + \ No newline at end of file diff --git a/SMMTracker.Tests/SMMtracker.Tests.csproj b/SMMTracker.Tests/SMMtracker.Tests.csproj new file mode 100644 index 0000000..2fd800c --- /dev/null +++ b/SMMTracker.Tests/SMMtracker.Tests.csproj @@ -0,0 +1,28 @@ + + + net8.0 + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + \ No newline at end of file diff --git a/SMMTracker.Tests/TeamRepositoryTests.cs b/SMMTracker.Tests/TeamRepositoryTests.cs new file mode 100644 index 0000000..a83d79a --- /dev/null +++ b/SMMTracker.Tests/TeamRepositoryTests.cs @@ -0,0 +1,86 @@ +using System; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Domain.Entities; +using SMMTracker.Infrastructure.Data.DataContext; +using SMMTracker.Infrastructure.Repositories; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Tests; + +public class TeamRepositoryTests : IAsyncLifetime +{ + private ApplicationDbContext _context; + private TeamRepository _repository; + + public async Task InitializeAsync() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: $"TestDb_Team_{Guid.NewGuid()}") + .Options; + + _context = new ApplicationDbContext(options); + await _context.Database.EnsureCreatedAsync(); + _repository = new TeamRepository(_context); + } + + public async Task DisposeAsync() + { + await _context.Database.EnsureDeletedAsync(); + await _context.DisposeAsync(); + } + + [Fact] + public async Task AddAsync_AddsTeamWithCode() + { + var team = new Team("Тестовая команда", "TEST123"); + + await _repository.AddAsync(team); + + var savedTeam = await _context.Teams.FirstOrDefaultAsync(); + savedTeam.Should().NotBeNull(); + savedTeam!.Name.Should().Be("Тестовая команда"); + savedTeam.Code.Should().Be("TEST123"); + } + + [Fact] + public async Task GetByCodeAsync_ReturnsCorrectTeam() + { + var team1 = new Team("Первая", "CODE111"); + var team2 = new Team("Вторая", "CODE222"); + + _context.Teams.AddRange(team1, team2); + await _context.SaveChangesAsync(); + + var result = await _repository.GetByCodeAsync("CODE222"); + + result.Should().NotBeNull(); + result!.Name.Should().Be("Вторая"); + result.Code.Should().Be("CODE222"); + } + + [Fact] + public async Task ExistsByCodeAsync_ReturnsCorrectValues() + { + var team = new Team("Команда", "EXISTS123"); + _context.Teams.Add(team); + await _context.SaveChangesAsync(); + + (await _repository.ExistsByCodeAsync("EXISTS123")).Should().BeTrue(); + (await _repository.ExistsByCodeAsync("NOTEXIST")).Should().BeFalse(); + } + + [Fact] + public async Task DeleteAsync_RemovesTeam() + { + var team = new Team("Удалить", "DEL123"); + _context.Teams.Add(team); + await _context.SaveChangesAsync(); + + await _repository.DeleteAsync(team.Id); + + var deletedTeam = await _context.Teams.FindAsync(team.Id); + deletedTeam.Should().BeNull(); + } +} \ No newline at end of file diff --git a/SMMTracker.Tests/UserRepositoryTests.cs b/SMMTracker.Tests/UserRepositoryTests.cs new file mode 100644 index 0000000..6aa967c --- /dev/null +++ b/SMMTracker.Tests/UserRepositoryTests.cs @@ -0,0 +1,175 @@ +using System; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Domain.Entities; +using SMMTracker.Infrastructure.Data.DataContext; +using SMMTracker.Infrastructure.Repositories; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Tests.IntegrationTests; + +public class UserRepositoryTests : IAsyncLifetime +{ + private ApplicationDbContext _context; + private UserRepository _repository; + + public async Task InitializeAsync() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: $"TestDb_User_{Guid.NewGuid()}") + .Options; + + _context = new ApplicationDbContext(options); + await _context.Database.EnsureCreatedAsync(); + _repository = new UserRepository(_context); + } + + public async Task DisposeAsync() + { + await _context.Database.EnsureDeletedAsync(); + await _context.DisposeAsync(); + } + + [Fact] + public async Task AddAsync_AddsUserToDatabase() + { + var user = new User + { + TelegramId = 123456789, + FirstName = "Тест", + LastName = "Юзер", + UserName = "testuser", + Hash = Guid.NewGuid().ToString() + }; + + await _repository.AddAsync(user); + + var savedUser = await _context.Users.FirstOrDefaultAsync(); + savedUser.Should().NotBeNull(); + savedUser!.FirstName.Should().Be("Тест"); + savedUser.LastName.Should().Be("Юзер"); + savedUser.TelegramId.Should().Be(123456789); + savedUser.UserName.Should().Be("testuser"); + savedUser.Hash.Should().NotBeNullOrEmpty(); + savedUser.Id.Should().BeGreaterThan(0); + } + + [Fact] + public async Task GetByIdAsync_ReturnsUser() + { + var user = new User + { + TelegramId = 111222333, + FirstName = "Анна", + LastName = "Петрова", + UserName = "annapetrova", + Hash = Guid.NewGuid().ToString() + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + var result = await _repository.GetByIdAsync(user.Id); + + result.Should().NotBeNull(); + result!.Id.Should().Be(user.Id); + result.FirstName.Should().Be("Анна"); + result.LastName.Should().Be("Петрова"); + result.UserName.Should().Be("annapetrova"); + result.Hash.Should().Be(user.Hash); + } + + [Fact] + public async Task GetByTelegramIdAsync_ReturnsCorrectUser() + { + var telegramId = 999888777L; + var user = new User + { + TelegramId = telegramId, + FirstName = "Поиск", + LastName = "Телеграм", + UserName = "telegramuser", + Hash = Guid.NewGuid().ToString() + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + var result = await _repository.GetByTelegramIdAsync(telegramId); + + result.Should().NotBeNull(); + result!.TelegramId.Should().Be(telegramId); + result.FirstName.Should().Be("Поиск"); + result.LastName.Should().Be("Телеграм"); + result.UserName.Should().Be("telegramuser"); + result.Hash.Should().Be(user.Hash); + } + + [Fact] + public async Task UpdateAsync_UpdatesUser() + { + var user = new User + { + TelegramId = 555555555, + FirstName = "Старое", + LastName = "Имя", + UserName = "olduser", + Hash = Guid.NewGuid().ToString() + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + + user.FirstName = "Новое"; + await _repository.UpdateAsync(user); + + var updatedUser = await _context.Users.FindAsync(user.Id); + updatedUser!.FirstName.Should().Be("Новое"); + updatedUser.LastName.Should().Be("Имя"); + updatedUser.UserName.Should().Be("olduser"); + updatedUser.Hash.Should().Be(user.Hash); + } + + [Fact] + public async Task DeleteAsync_RemovesUser() + { + var user = new User + { + TelegramId = 777777777, + FirstName = "Удалить", + LastName = "Меня", + UserName = "deleteuser", + Hash = Guid.NewGuid().ToString() + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + await _repository.DeleteAsync(user.Id); + var deletedUser = await _context.Users.FindAsync(user.Id); + deletedUser.Should().BeNull(); + } + + [Fact] + public async Task ExistsAsync_ReturnsTrueForExistingUser() + { + var user = new User + { + TelegramId = 888888888, + FirstName = "Существует", + LastName = "Пользователь", + UserName = "existinguser", + Hash = Guid.NewGuid().ToString() + }; + _context.Users.Add(user); + await _context.SaveChangesAsync(); + + var result = await _repository.ExistsAsync(user.Id); + result.Should().BeTrue(); + } + + [Fact] + public async Task ExistsAsync_ReturnsFalseForNonExistingUser() + { + var result = await _repository.ExistsAsync(999999); + result.Should().BeFalse(); + } +} \ No newline at end of file diff --git a/SMMTracker.Tests/UserTeamRepositoryTests.cs b/SMMTracker.Tests/UserTeamRepositoryTests.cs new file mode 100644 index 0000000..9bf7b04 --- /dev/null +++ b/SMMTracker.Tests/UserTeamRepositoryTests.cs @@ -0,0 +1,155 @@ +using System; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Domain.Entities; +using SMMTracker.Domain.Enums; +using SMMTracker.Infrastructure.Data.DataContext; +using SMMTracker.Infrastructure.Repositories; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace SMMTracker.Tests.IntegrationTests; + +public class UserTeamRepositoryTests : IAsyncLifetime +{ + private ApplicationDbContext _context; + private UserTeamRepository _repository; + private User _user1; + private User _user2; + private Team _team1; + private Team _team2; + + public async Task InitializeAsync() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: $"TestDb_UserTeam_{Guid.NewGuid()}") + .Options; + + _context = new ApplicationDbContext(options); + await _context.Database.EnsureCreatedAsync(); + + _repository = new UserTeamRepository(_context); + + _user1 = new User + { + TelegramId = 111, + FirstName = "Пользователь1", + LastName = "Тестовый1", + UserName = "user1test", + Hash = Guid.NewGuid().ToString() + }; + _user2 = new User + { + TelegramId = 222, + FirstName = "Пользователь2", + LastName = "Тестовый2", + UserName = "user2test", + Hash = Guid.NewGuid().ToString() + }; + _team1 = new Team("Команда1", "TEAM1"); + _team2 = new Team("Команда2", "TEAM2"); + + _context.Users.AddRange(_user1, _user2); + _context.Teams.AddRange(_team1, _team2); + await _context.SaveChangesAsync(); + } + + public async Task DisposeAsync() + { + await _context.Database.EnsureDeletedAsync(); + await _context.DisposeAsync(); + } + + [Fact] + public async Task AddAsync_CreatesUserTeamRelationship() + { + var userTeam = new UserTeam + { + UserId = _user1.Id, + TeamId = _team1.Id, + Role = TeamRole.Admin + }; + + await _repository.AddAsync(userTeam); + var saved = await _context.UserTeams.FirstOrDefaultAsync(); + saved.Should().NotBeNull(); + saved!.UserId.Should().Be(_user1.Id); + saved.TeamId.Should().Be(_team1.Id); + saved.Role.Should().Be(TeamRole.Admin); + } + + [Fact] + public async Task GetUserTeamAsync_ReturnsRelationship() + { + var userTeam = new UserTeam + { + UserId = _user1.Id, + TeamId = _team1.Id, + Role = TeamRole.User + }; + _context.UserTeams.Add(userTeam); + await _context.SaveChangesAsync(); + var result = await _repository.GetUserTeamAsync(_team1.Id, _user1.Id); + + result.Should().NotBeNull(); + result!.UserId.Should().Be(_user1.Id); + result.TeamId.Should().Be(_team1.Id); + } + + [Fact] + public async Task IsUserAdminAsync_ReturnsCorrectValues() + { + var adminUserTeam = new UserTeam + { + UserId = _user1.Id, + TeamId = _team1.Id, + Role = TeamRole.Admin + }; + + var regularUserTeam = new UserTeam + { + UserId = _user2.Id, + TeamId = _team1.Id, + Role = TeamRole.User + }; + + _context.UserTeams.AddRange(adminUserTeam, regularUserTeam); + await _context.SaveChangesAsync(); + + (await _repository.IsUserAdminAsync(_team1.Id, _user1.Id)).Should().BeTrue(); + (await _repository.IsUserAdminAsync(_team1.Id, _user2.Id)).Should().BeFalse(); + } + + [Fact] + public async Task DeleteAsync_RemovesUserTeam() + { + var userTeam = new UserTeam + { + UserId = _user1.Id, + TeamId = _team1.Id, + Role = TeamRole.Admin + }; + _context.UserTeams.Add(userTeam); + await _context.SaveChangesAsync(); + await _repository.DeleteAsync(userTeam.Id); + + var deleted = await _context.UserTeams.FindAsync(userTeam.Id); + deleted.Should().BeNull(); + } + + [Fact] + public async Task ExistsAsync_ReturnsCorrectValues() + { + var userTeam = new UserTeam + { + UserId = _user1.Id, + TeamId = _team1.Id, + Role = TeamRole.Admin + }; + _context.UserTeams.Add(userTeam); + await _context.SaveChangesAsync(); + + (await _repository.ExistsAsync(userTeam.Id)).Should().BeTrue(); + (await _repository.ExistsAsync(999999)).Should().BeFalse(); + } +} \ No newline at end of file diff --git a/SMMTracker.TgBot/Dockerfile b/SMMTracker.TgBot/Dockerfile new file mode 100644 index 0000000..372f819 --- /dev/null +++ b/SMMTracker.TgBot/Dockerfile @@ -0,0 +1,24 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["SMMTracker.TgBot/SMMTracker.TgBot.csproj", "SMMTracker.TgBot/"] +COPY ["SMMTracker.Application/SMMTracker.Application.csproj", "SMMTracker.Application/"] +COPY ["SMMTracker.Domain/SMMTracker.Domain.csproj", "SMMTracker.Domain/"] +COPY ["SMMTracker.Infrastructure/SMMTracker.Infrastructure.csproj", "SMMTracker.Infrastructure/"] +RUN dotnet restore "./SMMTracker.TgBot/SMMTracker.TgBot.csproj" +COPY . . +WORKDIR "/src/SMMTracker.TgBot" +RUN dotnet build "./SMMTracker.TgBot.csproj" -c $BUILD_CONFIGURATION -o /app/build --no-restore + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./SMMTracker.TgBot.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "SMMTracker.TgBot.dll"] \ No newline at end of file diff --git a/SMMTracker.TgBot/Program.cs b/SMMTracker.TgBot/Program.cs index eebd627..bc3ac51 100644 --- a/SMMTracker.TgBot/Program.cs +++ b/SMMTracker.TgBot/Program.cs @@ -1,61 +1,69 @@ using Microsoft.EntityFrameworkCore; -using SMMTracker.Application.Interfaces; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Services; using SMMTracker.Infrastructure.Data.DataContext; -using SMMTracker.Infrastructure.Services; -using SMMTracker.TgBot; +using SMMTracker.Infrastructure.Repositories; +using SMMTracker.Domain.IRepositories; namespace SMMTracker.TgBot; -class Program -{ - const string token = "8450218559:AAGCQdk6hnrtP8aFZpZM-bCc7tCWeKNWaIE"; +static class Program +{ public static async Task Main() { - var solutionDir = Directory.GetParent(AppContext.BaseDirectory) - .Parent.Parent.Parent.Parent.FullName; - - var dbPath = Path.Combine(solutionDir, "SharedDatabase", "DataBase.db"); + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: false) + .AddJsonFile("appsettings.Secrets.json", optional: false) + .Build(); - Console.WriteLine("DB Path: " + dbPath); - var dir = Path.GetDirectoryName(dbPath); - Console.WriteLine("Folder exists: " + Directory.Exists(dir)); + var token = configuration["Telegram:BotToken"]; - if (!Directory.Exists(dir)) + if (string.IsNullOrEmpty(token)) { - Console.WriteLine("Creating directory manually..."); - Directory.CreateDirectory(dir); + Console.WriteLine("Токен не найден в файле appsettings.Secrets.json"); + return; } - var connectionString = $"Data Source={dbPath}"; - var options = new DbContextOptionsBuilder() - .UseSqlite(connectionString) - .Options; + Console.WriteLine($"Токен найден"); - var context = new ApplicationDbContext(options); - await context.Database.MigrateAsync(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); - IUserManager userManager = new UserManager(context); + if (string.IsNullOrEmpty(connectionString)) + { + Console.WriteLine("путь к бд не найден в конфиге"); + return; + } - var bot = new TelegramBotService(token, userManager); - await bot.StartAsync(CancellationToken.None); + Console.WriteLine($"путь к бд {connectionString}"); - Console.WriteLine("Бот запущен. Нажмите любую клавишу для выхода..."); - Console.ReadKey(); + var services = new ServiceCollection(); - Console.WriteLine("\nСодержимое базы данных:"); - Console.WriteLine(await ShowDbAsync(context)); - } + services.AddDbContext(options => + options.UseSqlite(connectionString)); + + services.AddScoped(provider => + provider.GetRequiredService()); + + services.AddScoped(); + services.AddScoped(); - private static async Task ShowDbAsync(ApplicationDbContext context) - { - var users = await context.Users.ToListAsync(); - if (!users.Any()) - return "База данных пуста"; + var serviceProvider = services.BuildServiceProvider(); + + try + { + var userService = serviceProvider.GetRequiredService(); + var bot = new TelegramBotService(token, userService); + + await bot.StartAsync(CancellationToken.None); - var list = users.Select(u => - $"ID: {u.Id}, TelegramId: {u.TelegramId}, Имя: {u.FirstName}, Фамилия: {u.LastName}, Username: {u.TelegramUsername}" - ); + Console.WriteLine("Бот запущен"); + Console.WriteLine("/start в @SmmTrackerTestBot_bot"); - return string.Join("\n", list); + await Task.Delay(-1, CancellationToken.None); + } + catch (Exception ex) + { + Console.WriteLine($"Ошибка: {ex.Message}"); + } } -} \ No newline at end of file +} diff --git a/SMMTracker.TgBot/SMMTracker.TgBot.csproj b/SMMTracker.TgBot/SMMTracker.TgBot.csproj index 197f111..7ad7704 100644 --- a/SMMTracker.TgBot/SMMTracker.TgBot.csproj +++ b/SMMTracker.TgBot/SMMTracker.TgBot.csproj @@ -1,5 +1,6 @@ + false net8.0 SMMTracker.TgBot enable diff --git a/SMMTracker.TgBot/TelegramBotService.cs b/SMMTracker.TgBot/TelegramBotService.cs index 1da6e7a..b29ca29 100644 --- a/SMMTracker.TgBot/TelegramBotService.cs +++ b/SMMTracker.TgBot/TelegramBotService.cs @@ -1,8 +1,7 @@ -using SMMTracker.Application.Interfaces; +using SMMTracker.Application.Abstractions; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Types; -using SMMTracker.Domain.Entities; using Task = System.Threading.Tasks.Task; using User = SMMTracker.Domain.Entities.User; @@ -11,47 +10,82 @@ namespace SMMTracker.TgBot; public class TelegramBotService { private readonly TelegramBotClient _client; - private readonly IUserManager _userManager; + private readonly IUserService _userService; - public TelegramBotService(string token, IUserManager userManager) + public TelegramBotService(string token, IUserService userService) { _client = new TelegramBotClient(token); - _userManager = userManager; + _userService = userService; } - private async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + private async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, + CancellationToken cancellationToken) { if (update.Message == null) return; - + var chatId = update.Message.Chat.Id; var tgUser = update.Message.From; - var user = new User + + if (tgUser == null) + return; + + var messageText = update.Message.Text ?? string.Empty; + + if (messageText.Equals("/start", StringComparison.OrdinalIgnoreCase)) { - TelegramId = tgUser.Id, - FirstName = tgUser.FirstName ?? "Unknown", - LastName = tgUser.LastName ?? "", - UserName = tgUser.Username ?? "" - }; - var userDto = await _userManager.FindOrCreateUserAsync(user); + await _client.SendMessage( + chatId: chatId, + text: "Привет! Я бот для авторизации в SMM Tracker.\n\n" + + "Напишите любое сообщение для регистрации в системе.", cancellationToken: cancellationToken); + return; + } - await botClient.SendMessage( - chatId: chatId, - text: $"Добро пожаловать, {userDto.FirstName}! Вы успешно авторизованы.", - cancellationToken: cancellationToken - ); + try + { + var user = new User + { + TelegramId = tgUser.Id, + FirstName = tgUser.FirstName, + LastName = tgUser.LastName ?? "", + UserName = tgUser.Username ?? "" + }; + + var userDto = await _userService.FindOrCreateUserAsync(user); + + await _client.SendMessage( + chatId: chatId, + text: $"Добро пожаловать, {userDto.FirstName}! Вы успешно авторизованы.\n", + cancellationToken: cancellationToken); + } + catch (Exception ex) + { + Console.WriteLine($"Ошибка обработки сообщения: {ex.Message}"); + + try + { + await _client.SendMessage( + chatId: chatId, + text: "Произошла ошибка при обработке вашего запроса.", cancellationToken: cancellationToken); + } + catch + { + // ignored + } + } } - private Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) + private static Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, + CancellationToken cancellationToken) { - var ErrorMessage = exception switch + var errorMessage = exception switch { ApiRequestException apiRequestException => $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}", _ => exception.ToString() }; - Console.WriteLine(ErrorMessage); + Console.WriteLine(errorMessage); return Task.CompletedTask; } @@ -62,8 +96,7 @@ public async Task StartAsync(CancellationToken cancellationToken) HandleErrorAsync, cancellationToken: cancellationToken ); - - var me = await _client.GetMe(); - Console.WriteLine($"Бот @{me.Username} запущен!"); + + var me = await _client.GetMe(cancellationToken: cancellationToken); } -} \ No newline at end of file +} diff --git a/SMMTracker.TgBot/appsettings.Secrets.json b/SMMTracker.TgBot/appsettings.Secrets.json new file mode 100644 index 0000000..8536f4b --- /dev/null +++ b/SMMTracker.TgBot/appsettings.Secrets.json @@ -0,0 +1,6 @@ +{ + "Telegram": { + "BotName": "SmmTrackerTestBot_bot", + "BotToken": "7770947912:AAHRHg0PiUe3PrjFatQ_X4THIwUZs5HZUJ0" + } +} \ No newline at end of file diff --git a/SMMTracker.TgBot/appsettings.json b/SMMTracker.TgBot/appsettings.json new file mode 100644 index 0000000..884ca77 --- /dev/null +++ b/SMMTracker.TgBot/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=/app/data/SMMTracker.db;Cache=Shared;Pooling=true" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/SMMTracker.WebUI/API/AchievementsApiController.cs b/SMMTracker.WebUI/API/AchievementsApiController.cs new file mode 100644 index 0000000..cd2b8ce --- /dev/null +++ b/SMMTracker.WebUI/API/AchievementsApiController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using SMMTracker.Application.Abstractions; +using System.Security.Claims; + +namespace SMMTracker.WebUI.Controllers; + +[Route("api/check-achievements")] +[ApiController] +public class AchievementsApiController : ControllerBase +{ + private readonly IAchievementService _service; + + public AchievementsApiController(IAchievementService service) + { + _service = service; + } + + [HttpGet] + public async Task GetNewAchievements() + { + var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (int.TryParse(userIdString, out int userId)) + { + var newAchievements = await _service.GetUserNewAchievementsAsync(userId); + return Ok(newAchievements); + } + return Ok(new List()); + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/API/AuthController.cs b/SMMTracker.WebUI/API/AuthController.cs new file mode 100644 index 0000000..01984e5 --- /dev/null +++ b/SMMTracker.WebUI/API/AuthController.cs @@ -0,0 +1,232 @@ +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; +using SMMTracker.Domain.Entities; +using SMMTracker.Infrastructure.Data.DataContext; + +namespace SMMTracker.WebUI.API; + +[ApiController] +[AllowAnonymous] +public class AuthController : ControllerBase +{ + private readonly IUserService _userService; + private readonly ApplicationDbContext _context; + private readonly IConfiguration _configuration; + + public AuthController(IUserService userService, ApplicationDbContext context, IConfiguration configuration) + { + _userService = userService; + _context = context; + _configuration = configuration; + } + + [HttpGet("/api/auth/telegram-callback")] + public async Task TelegramCallback( + [FromQuery] long id, + [FromQuery] string first_name, + [FromQuery] string? last_name, + [FromQuery] string? username, + [FromQuery] string? photo_url, + [FromQuery] long auth_date, + [FromQuery] string hash) + { + try + { + var connection = (SqliteConnection)_context.Database.GetDbConnection(); + Console.WriteLine($"[TELEGRAM_AUTH_DEBUG] База данных: {connection.DataSource}"); + Console.WriteLine($"[TELEGRAM_AUTH_DEBUG] Данные пользователя: {id}, {first_name}, {username}"); + + var botToken = _configuration["Telegram:BotToken"]; + if (string.IsNullOrEmpty(botToken)) + { + Console.WriteLine("[TELEGRAM_AUTH_ERROR] Токен бота не найден в конфигурации"); + return Redirect("/Login?error=auth_failed"); + } + + var isValid = ValidateTelegramData(botToken, id, first_name, last_name, + username, photo_url, auth_date, hash); + + if (!isValid) + { + Console.WriteLine($"[TELEGRAM_AUTH_ERROR] Неверная подпись данных"); + return Redirect("/Login?error=invalid_data"); + } + + var authDateTime = DateTimeOffset.FromUnixTimeSeconds(auth_date); + if (DateTimeOffset.UtcNow - authDateTime > TimeSpan.FromMinutes(1)) + { + Console.WriteLine($"[TELEGRAM_AUTH_ERROR] Данные устарели: {authDateTime}"); + return Redirect("/Login?error=timeout"); + } + + var user = new User + { + TelegramId = id, + FirstName = first_name, + LastName = last_name ?? "", + UserName = username ?? $"user_{id}", + Hash = Guid.NewGuid().ToString() + }; + + var appUser = await _userService.FindOrCreateUserAsync(user); + Console.WriteLine($"[TELEGRAM_AUTH_SUCCESS] Пользователь авторизован: {appUser.Id}, {appUser.UserName}"); + + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, appUser.Id.ToString()), + new Claim(ClaimTypes.Name, appUser.UserName), + new Claim("TelegramId", appUser.TelegramId.ToString()), + new Claim("FirstName", appUser.FirstName), + new Claim("LastName", appUser.LastName ?? "") + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30) + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + return Redirect("/Dashboard"); + } + catch (Exception ex) + { + Console.WriteLine($"[TELEGRAM_AUTH_ERROR] Ошибка: {ex.Message}"); + if (ex.InnerException != null) + { + Console.WriteLine($"[TELEGRAM_AUTH_ERROR] Внутренняя ошибка: {ex.InnerException.Message}"); + } + + return Redirect("/Login?error=auth_failed"); + } + } + + private bool ValidateTelegramData(string botToken, long id, string firstName, + string? lastName, string? username, string? photoUrl, long authDate, string hash) + { + try + { + Console.WriteLine($"[VALIDATE_DEBUG] Токен: {botToken[..15]}..."); + + var dataCheckDict = new Dictionary + { + ["auth_date"] = authDate.ToString(), + ["first_name"] = firstName, + ["id"] = id.ToString() + }; + + if (!string.IsNullOrEmpty(lastName)) + dataCheckDict["last_name"] = lastName; + + if (!string.IsNullOrEmpty(photoUrl)) + dataCheckDict["photo_url"] = photoUrl; + + if (!string.IsNullOrEmpty(username)) + dataCheckDict["username"] = username; + + var sortedKeys = dataCheckDict.Keys.OrderBy(k => k).ToList(); + + var dataCheckArray = new List(); + foreach (var key in sortedKeys) + dataCheckArray.Add($"{key}={dataCheckDict[key]}"); + + var dataCheckString = string.Join("\n", dataCheckArray); + + Console.WriteLine($"[VALIDATE_DEBUG] Data string для хеша:"); + Console.WriteLine($"\"{dataCheckString.Replace("\n", "\\n")}\""); + Console.WriteLine($"[VALIDATE_DEBUG] Ожидаемый хеш: {hash}"); + + byte[] secretKey; + using (var sha256 = SHA256.Create()) + { + secretKey = sha256.ComputeHash(Encoding.UTF8.GetBytes(botToken)); + } + + using var hmac = new HMACSHA256(secretKey); + var computedHashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataCheckString)); + var computedHashString = BitConverter.ToString(computedHashBytes) + .Replace("-", "") + .ToLower(); + + Console.WriteLine($"[VALIDATE_DEBUG] Вычисленный хеш: {computedHashString}"); + Console.WriteLine($"[VALIDATE_DEBUG] Совпадают: {computedHashString == hash.ToLower()}"); + + Console.WriteLine($"[VALIDATE_DEBUG] Альтернативный расчет с raw токеном..."); + using var hmac2 = new HMACSHA256(Encoding.UTF8.GetBytes(botToken)); + var computedHash2 = BitConverter.ToString(hmac2.ComputeHash(Encoding.UTF8.GetBytes(dataCheckString))) + .Replace("-", "") + .ToLower(); + Console.WriteLine($"[VALIDATE_DEBUG] С raw токеном: {computedHash2}"); + + return computedHashString == hash.ToLower() || computedHash2 == hash.ToLower(); + } + catch (Exception ex) + { + Console.WriteLine($"[VALIDATE_ERROR] {ex.Message}"); + return false; + } + } + + [HttpPost("api/auth/telegram")] + public async Task LoginWithTelegram([FromBody] TelegramLoginDto? dto) + { + var connection = (SqliteConnection)_context.Database.GetDbConnection(); + Console.WriteLine($"[AUTH_CONTROLLER_DEBUG] Сайт использует базу данных: {connection.DataSource}"); + + if (dto == null) + return BadRequest("No dto received"); + + var user = new User + { + TelegramId = dto.Id, + FirstName = dto.FirstName, + LastName = dto.LastName, + UserName = string.IsNullOrWhiteSpace(dto.Username) ? $"user_{dto.Id}" : dto.Username, + Hash = Guid.NewGuid().ToString() + }; + + try + { + var appUser = await _userService.FindOrCreateUserAsync(user); + + var claims = new List + { + new(ClaimTypes.NameIdentifier, appUser.Id.ToString()), + new(ClaimTypes.Name, appUser.UserName) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30) + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + return Ok(appUser); + } + catch (Exception ex) + { + Console.WriteLine($"[AUTH_CONTROLLER_ERROR] Ошибка при сохранении пользователя: {ex.Message}"); + return StatusCode(500, "Internal server error"); + } + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/API/CalendarsController.cs b/SMMTracker.WebUI/API/CalendarsController.cs index 4fa7899..5a2e849 100644 --- a/SMMTracker.WebUI/API/CalendarsController.cs +++ b/SMMTracker.WebUI/API/CalendarsController.cs @@ -1,19 +1,21 @@ -using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; using Microsoft.AspNetCore.Mvc; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; using SMMTracker.Application.Services; -namespace BlazorApp1.API; +namespace SMMTracker.WebUI.API; [ApiController] -[Route("api/[controller]")] +[Route("api/[controller]")] public class CalendarsController : ControllerBase { - private readonly CalendarService _calendarService; + private readonly ICalendarService _calendarService; + private readonly IEventService _eventService; - public CalendarsController(CalendarService calendarService) + public CalendarsController(ICalendarService calendarService, IEventService eventService) { _calendarService = calendarService; + _eventService = eventService; } [HttpPost] @@ -22,4 +24,11 @@ public async Task CreateCalendar([FromBody] CreateCalendarDto dto var calendarId = await _calendarService.CreateCalendarAsync(dto); return Ok(new { CalendarId = calendarId }); } + + [HttpGet("{calendarId}/events")] + public async Task GetCalendarEvents(int calendarId, [FromQuery] int month, [FromQuery] int year) + { + var monthEvents = await _eventService.GetEventsForMonthAsync(calendarId, month, year); + return Ok(monthEvents); + } } \ No newline at end of file diff --git a/SMMTracker.WebUI/API/EventsController.cs b/SMMTracker.WebUI/API/EventsController.cs index c975aac..3c33496 100644 --- a/SMMTracker.WebUI/API/EventsController.cs +++ b/SMMTracker.WebUI/API/EventsController.cs @@ -1,18 +1,17 @@ -using Microsoft.AspNetCore.Http.HttpResults; -using SMMTracker.Application.Dtos; -using SMMTracker.Application.Interfaces; using Microsoft.AspNetCore.Mvc; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; using SMMTracker.Application.Services; -namespace BlazorApp1.API; +namespace SMMTracker.WebUI.API; [ApiController] [Route("api/[controller]")] public class EventsController : ControllerBase { - private readonly EventService _eventService; + private readonly IEventService _eventService; - public EventsController(EventService eventService) + public EventsController(IEventService eventService) { _eventService = eventService; } @@ -23,4 +22,12 @@ public async Task CreateEvent([FromBody] CreateEventDto dto) var eventId = await _eventService.CreateEventAsync(dto); return Ok(new { EventId = eventId }); } + + [HttpGet("{eventId}")] + public async Task GetEventDetails(int eventId) + { + var eventDetails = await _eventService.GetEventDetailsAsync(eventId); + if (eventDetails == null) return NotFound("Event not found"); + return Ok(eventDetails); + } } \ No newline at end of file diff --git a/SMMTracker.WebUI/API/TasksController.cs b/SMMTracker.WebUI/API/TasksController.cs index f3a6b73..805b5ae 100644 --- a/SMMTracker.WebUI/API/TasksController.cs +++ b/SMMTracker.WebUI/API/TasksController.cs @@ -1,16 +1,17 @@ using Microsoft.AspNetCore.Mvc; -using SMMTracker.Application.Services; +using SMMTracker.Application.Abstractions; using SMMTracker.Application.Dtos; +using SMMTracker.Application.Services; -namespace BlazorApp1.API; +namespace SMMTracker.WebUI.API; [ApiController] [Route("api/[controller]")] public class TasksController : ControllerBase { - private readonly TaskService _taskService; + private readonly ITaskService _taskService; - public TasksController(TaskService taskService) + public TasksController(ITaskService taskService) { _taskService = taskService; } @@ -22,12 +23,68 @@ public async Task CreateTask([FromBody] CreateTaskDto createTaskD return Ok(new { TaskId = taskId }); } + [HttpPost("{taskId}/move-to-review")] + public async Task MoveTaskToReview(int taskId) + { + try + { + await _taskService.MoveTaskToReviewAsync(taskId); + return Ok(); + } + catch (Exception ex) + { + return NotFound(new { message = ex.Message }); + } + } + + [HttpPost("{taskId}/move-to-done")] + public async Task MoveTaskToDone(int taskId) + { + try + { + await _taskService.MoveTaskToDoneAsync(taskId); + return Ok(); + } + catch (Exception ex) + { + return NotFound(new { message = ex.Message }); + } + } + + [HttpPost("{taskId}/move-to-progress")] + public async Task MoveTaskToProgress(int taskId) + { + try + { + await _taskService.MoveTaskToProgressAsync(taskId); + return Ok(); + } + catch (Exception ex) + { + return BadRequest(new { message = ex.Message }); + } + } + [HttpDelete("{taskId}")] public async Task RemoveTask(int taskId) { try { - await _taskService.RemoveTaskAsync(taskId); + await _taskService.DeleteTaskAsync(taskId); + return NoContent(); + } + catch (Exception ex) + { + return NotFound(new { message = ex.Message }); + } + } + + [HttpPost("{taskId}/change_name")] + public async Task ChangeTaskName(int taskId, [FromBody] ChangeTaskNameDto dto) + { + try + { + await _taskService.ChangeTaskNameAsync(taskId, dto.Name); return NoContent(); } catch (Exception ex) @@ -35,4 +92,50 @@ public async Task RemoveTask(int taskId) return NotFound(new { message = ex.Message }); } } + + [HttpPost("{taskId}/change_description")] + public async Task ChangeTaskDescription(int taskId, [FromBody] ChangeTaskDescriptionDto dto) + { + try + { + await _taskService.ChangeTaskDescriptionAsync(taskId, dto.Description); + return NoContent(); + } + catch (Exception ex) + { + return NotFound(new { message = ex.Message }); + } + } + + [HttpPost("{taskId}/set_deadline")] + public async Task SetTaskDeadline(int taskId, [FromBody] SetTaskDeadlineDto dto) + { + try + { + await _taskService.SetTaskDeadlineAsync(taskId, dto.Deadline); + return NoContent(); + } + catch (Exception ex) + { + return NotFound(new { message = ex.Message }); + } + } + + [HttpPost("{taskId}/assign")] + public async Task AssignUserToTask(int taskId, [FromBody] AssignUserDto dto) + { + try + { + await _taskService.AssignUserToTaskAsync(taskId, dto.UserId, dto.AdminId); + return Ok(); + } + catch (UnauthorizedAccessException e) + { + return Forbid(e.Message); + } + catch (Exception e) + { + return BadRequest(e.Message); + } + } } \ No newline at end of file diff --git a/SMMTracker.WebUI/API/TeamsController.cs b/SMMTracker.WebUI/API/TeamsController.cs index 23f8861..b49c40b 100644 --- a/SMMTracker.WebUI/API/TeamsController.cs +++ b/SMMTracker.WebUI/API/TeamsController.cs @@ -1,25 +1,59 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using SMMTracker.Application.Services; +using SMMTracker.Application.Abstractions; using SMMTracker.Application.Dtos; +using SMMTracker.Application.Services; -namespace BlazorApp1.API; +namespace SMMTracker.WebUI.API; [ApiController] [Route("api/[controller]")] +[Authorize] public class TeamsController : ControllerBase { - private readonly TeamService _teamService; + private readonly ITeamService _teamService; - public TeamsController(TeamService teamService) + public TeamsController(ITeamService teamService) { - _teamService = teamService; - + _teamService = teamService; } [HttpPost] public async Task CreateTeam([FromBody] CreateTeamDto dto) { - var teamId = await _teamService.CreateTeamAsync(dto); + var creatorIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(creatorIdString) || !int.TryParse(creatorIdString, out var creatorId)) + { + return Unauthorized("Invalid user identifier"); + } + + var teamId = await _teamService.CreateTeamAsync(dto, creatorId); return Ok(new { TeamId = teamId }); } + + [HttpPost("join")] + public async Task JoinTeam([FromBody] JoinTeamDto dto) + { + var ok = await _teamService.JoinTeamAsync(dto); + return ok ? Ok() : BadRequest("Invalid team code"); + } + + [HttpPost("{teamId}/remove-user")] + public async Task RemoveUser(int teamId, [FromBody] RemoveUserDto dto) + { + try + { + await _teamService.RemoveUserFromTeamAsync(teamId, dto.UserId, dto.AdminId); + return Ok(); + } + catch (UnauthorizedAccessException ex) + { + return Forbid(ex.Message); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } } \ No newline at end of file diff --git a/SMMTracker.WebUI/API/UserController.cs b/SMMTracker.WebUI/API/UserController.cs deleted file mode 100644 index 4c02c5f..0000000 --- a/SMMTracker.WebUI/API/UserController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using SMMTracker.Infrastructure.Data.DataContext; - -namespace BlazorApp1.API; - -[ApiController] -[Route("api/[controller]")] -public class UserController : ControllerBase -{ - private readonly ApplicationDbContext context; - - public UserController(ApplicationDbContext context) - { - this.context = context; - } - - [HttpGet] - public IActionResult GetHello() - { - return Ok("GET"); - } - - -} \ No newline at end of file diff --git a/SMMTracker.WebUI/API/UsersController.cs b/SMMTracker.WebUI/API/UsersController.cs new file mode 100644 index 0000000..36a6d0b --- /dev/null +++ b/SMMTracker.WebUI/API/UsersController.cs @@ -0,0 +1,69 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; + +namespace SMMTracker.WebUI.API; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class UsersController : ControllerBase +{ + private readonly IUserService _userService; + private readonly ITeamService _teamService; + + public UsersController(IUserService userService, ITeamService teamService) + { + _userService = userService; + _teamService = teamService; + } + + [HttpGet("{userId:int}")] + public async Task GetUserProfile(int userId) + { + try + { + var profile = await _userService.GetUserProfileAsync(userId); + return Ok(profile); + } + catch (Exception e) + { + return StatusCode(500, new { e.Message }); + } + } + + [HttpPut("me")] + public async Task UpdateMyProfile([FromBody] UserProfileDto request) + { + try + { + var userId = GetUserId(); + await _userService.UpdateUserProfileAsync( + userId, + request); + + return Ok(new { Message = "Профиль успешно обновлен" }); + } + catch (Exception e) + { + return StatusCode(500, new { e.Message }); + } + } + + [HttpGet("{userId}/teams")] + public async Task GetTeamsForUserAsync(int userId) + { + var teams = await _teamService.GetTeamsForUserAsync(userId); + return Ok(teams); + } + + private int GetUserId() + { + var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out var userId)) + throw new UnauthorizedAccessException("Пользователь не авторизован"); + return userId; + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/App.razor b/SMMTracker.WebUI/App.razor deleted file mode 100644 index c7730d1..0000000 --- a/SMMTracker.WebUI/App.razor +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
\ No newline at end of file diff --git a/SMMTracker.WebUI/AuthController.cs b/SMMTracker.WebUI/AuthController.cs deleted file mode 100644 index 269233d..0000000 --- a/SMMTracker.WebUI/AuthController.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Data; -using SMMTracker.Infrastructure.Data.DataContext; -using SMMTracker.Domain.Entities; -using SMMTracker.Infrastructure.Services; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using BlazorApp1.DTOs; - -namespace BlazorApp1; - -[ApiController] -public class AuthController : ControllerBase -{ - private readonly UserManager _userManager; - private readonly ApplicationDbContext _context; - - public AuthController(UserManager userManager, ApplicationDbContext context) - { - _userManager = userManager; - _context = context; - } - - [HttpPost("api/auth/telegram")] - public async Task LoginWithTelegram([FromBody] TelegramLoginData data) - { - var connection = (SqliteConnection)_context.Database.GetDbConnection(); - Console.WriteLine($"[AUTH_CONTROLLER_DEBUG] Сайт использует базу данных: {connection.DataSource}"); - - if (data == null) - { - return BadRequest("No data received"); - } - - var user = new User - { - TelegramId = data.Id, - FirstName = data.FirstName, - LastName = data.LastName, - UserName = string.IsNullOrWhiteSpace(data.Username) ? $"user_{data.Id}" : data.Username, - Hash = Guid.NewGuid().ToString() - }; - - try - { - var appUser = await _userManager.FindOrCreateUserAsync(user); - return Ok(appUser); - } - catch (Exception ex) - { - Console.WriteLine($"[AUTH_CONTROLLER_ERROR] Ошибка при сохранении пользователя: {ex.Message}"); - return StatusCode(500, "Internal server error"); - } - } -} \ No newline at end of file diff --git a/SMMTracker.WebUI/Dockerfile b/SMMTracker.WebUI/Dockerfile index aa5be20..dd5fe5d 100644 --- a/SMMTracker.WebUI/Dockerfile +++ b/SMMTracker.WebUI/Dockerfile @@ -1,18 +1,33 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["BlazorApp1/BlazorApp1.csproj", "BlazorApp1/"] -COPY ["Data/Data.csproj", "Data/"] -COPY ["TgBot/TgBot.csproj", "TgBot/"] -COPY ["BlazorApp1.sln", "./"] - -RUN dotnet restore BlazorApp1.sln +COPY ["SMMTracker.WebUI/SMMTracker.WebUI.csproj", "SMMTracker.WebUI/"] +COPY ["SMMTracker.Application/SMMTracker.Application.csproj", "SMMTracker.Application/"] +COPY ["SMMTracker.Domain/SMMTracker.Domain.csproj", "SMMTracker.Domain/"] +COPY ["SMMTracker.Infrastructure/SMMTracker.Infrastructure.csproj", "SMMTracker.Infrastructure/"] +COPY ["SMMTracker.TgBot/SMMTracker.TgBot.csproj", "SMMTracker.TgBot/"] +RUN dotnet restore "./SMMTracker.WebUI/SMMTracker.WebUI.csproj" COPY . . +WORKDIR "/src/SMMTracker.WebUI" + +RUN dotnet publish "./SMMTracker.WebUI.csproj" \ + -c $BUILD_CONFIGURATION \ + -o /app/publish \ + /p:UseAppHost=false \ + --no-restore \ + /p:UseSharedCompilation=false \ + /p:M=1 -RUN dotnet publish BlazorApp1/BlazorApp1.csproj -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +FROM base AS final WORKDIR /app -COPY --from=build /src/BlazorApp1/bin/Release/net8.0/publish/ . -ENTRYPOINT ["dotnet", "BlazorApp1.dll"] \ No newline at end of file +COPY --from=build /app/publish . +ENTRYPOINT ["dotnet", "SMMTracker.WebUI.dll"] \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Achievements.cshtml b/SMMTracker.WebUI/Pages/Achievements.cshtml new file mode 100644 index 0000000..6d01a2f --- /dev/null +++ b/SMMTracker.WebUI/Pages/Achievements.cshtml @@ -0,0 +1,46 @@ +@page +@model SMMTracker.WebUI.Pages.AchievementsModel +@{ + ViewData["Title"] = "Достижения"; +} + +
+

Достижения

+ +
+ @foreach (var ach in Model.AllAchievements) + { + var isUnlocked = Model.UserAchievements.Any(ua => ua.Id == ach.Id); + var dateUnlocked = isUnlocked ? "Получено" : null; + var cardClass = isUnlocked ? "glass-card border-warning border-2" : "glass-card opacity-75"; + var textClass = isUnlocked ? "text-dark" : "text-muted"; + var iconColor = isUnlocked ? "text-warning" : "text-secondary"; + +
+
+
+
+ +
+
+
@ach.Title
+ @ach.Description +
+
+ @if (isUnlocked) + { +
+ Получено +
+ } + else + { +
+ @ach.TasksThreshold команд +
+ } +
+
+ } +
+
\ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Achievements.cshtml.cs b/SMMTracker.WebUI/Pages/Achievements.cshtml.cs new file mode 100644 index 0000000..9236090 --- /dev/null +++ b/SMMTracker.WebUI/Pages/Achievements.cshtml.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using SMMTracker.Application.Abstractions; +using SMMTracker.Domain.Entities; +using System.Security.Claims; + +namespace SMMTracker.WebUI.Pages; + +public class AchievementsModel : PageModel +{ + private readonly IAchievementService _achievementService; + + public List AllAchievements { get; set; } = new(); + public List UserAchievements { get; set; } = new(); + + public AchievementsModel(IAchievementService achievementService) + { + _achievementService = achievementService; + } + + public async System.Threading.Tasks.Task OnGetAsync() + { + var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + // Если ID в клеймах не тот, используй свой способ получения ID, например User.Identity.Name + if (int.TryParse(userIdString, out int userId)) + { + AllAchievements = await _achievementService.GetAllAchievementsAsync(); + UserAchievements = await _achievementService.GetUserAchievementsAsync(userId); + } + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Calendar.cshtml b/SMMTracker.WebUI/Pages/Calendar.cshtml new file mode 100644 index 0000000..bfd79c7 --- /dev/null +++ b/SMMTracker.WebUI/Pages/Calendar.cshtml @@ -0,0 +1,174 @@ +@page +@model SMMTracker.WebUI.Pages.CalendarModel +@{ + ViewData["Title"] = "Календарь"; +} + +
+
+
+ +

Загрузка...

+ +
+ +
+
Пн
+
Вт
+
Ср
+
Чт
+
Пт
+
Сб
+
Вс
+
+
+
+

+ + *данный раздел на данный момент находится в разработке :) +

+
+
+ +@section Scripts { + +} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Calendar.cshtml.cs b/SMMTracker.WebUI/Pages/Calendar.cshtml.cs new file mode 100644 index 0000000..49ef6eb --- /dev/null +++ b/SMMTracker.WebUI/Pages/Calendar.cshtml.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using SMMTracker.Application.Abstractions; +using System.Security.Claims; + +namespace SMMTracker.WebUI.Pages +{ + [Authorize] + public class CalendarModel : PageModel + { + private readonly ICalendarService _calendarService; + + [BindProperty] public int CurrentCalendarId { get; set; } + public int TeamId { get; set; } + + public CalendarModel(ICalendarService calendarService) + { + _calendarService = calendarService; + } + + public async Task OnGetAsync() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + + if (string.IsNullOrEmpty(userId)) + { + return RedirectToPage("/Account/Login"); + } + + var info = await _calendarService.GetCalendarInfoByUserIdAsync(userId); + + CurrentCalendarId = info.CalendarId; + TeamId = info.TeamId; + + return Page(); + } + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Dashboard.cshtml b/SMMTracker.WebUI/Pages/Dashboard.cshtml new file mode 100644 index 0000000..635702c --- /dev/null +++ b/SMMTracker.WebUI/Pages/Dashboard.cshtml @@ -0,0 +1,443 @@ +@page +@model SMMTracker.WebUI.ViewModels.DashboardModel +@{ + ViewData["Title"] = "Dashboard"; + Layout = "_Layout"; +} + + + +
+ @if (TempData["SuccessMessage"] != null) + { +
+ +
+ } + + @if (TempData["ErrorMessage"] != null) + { +
+ +
+ } + +
+
+ + +
+
+

+ Мои команды +

+ +
+ +
+
+
Присоединиться к команде
+
+
+
+ @Html.AntiForgeryToken() +
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +@* *@ + + + + + + diff --git a/SMMTracker.WebUI/Pages/Error.cshtml b/SMMTracker.WebUI/Pages/Error.cshtml deleted file mode 100644 index 33660b5..0000000 --- a/SMMTracker.WebUI/Pages/Error.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@page -@model SMMTracker.WebUI.Pages.ErrorModel - - - - - - - - Error - - - - - -
-
-

Error.

-

An error occurred while processing your request.

- - @if (Model.ShowRequestId) - { -

- Request ID: @Model.RequestId -

- } - -

Development Mode

-

- Swapping to the Development environment displays detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

-
-
- - - \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Error.cshtml.cs b/SMMTracker.WebUI/Pages/Error.cshtml.cs deleted file mode 100644 index 0627696..0000000 --- a/SMMTracker.WebUI/Pages/Error.cshtml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace SMMTracker.WebUI.Pages; - -[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] -[IgnoreAntiforgeryToken] -public class ErrorModel : PageModel -{ - public string? RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } -} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Event.cshtml b/SMMTracker.WebUI/Pages/Event.cshtml new file mode 100644 index 0000000..8883084 --- /dev/null +++ b/SMMTracker.WebUI/Pages/Event.cshtml @@ -0,0 +1,491 @@ +@page "/Event/{teamId}/{eventId}" +@model SMMTracker.WebUI.ViewModels.EventModel +@using TaskStatus = SMMTracker.Domain.Enums.TaskStatus; + +@{ + ViewData["Title"] = Model.Event.Title; + Layout = "_Layout"; +} + + + +
+
+
+
+
+ +

@Model.Event.Title

+

@Model.Event.Description

+
+
+
+
+ + + + @if (TempData["SuccessMessage"] != null) + { +
+ +
+ } + + @if (TempData["ErrorMessage"] != null) + { +
+ +
+ } + +
+
+
+
+
+

+ @Model.Tasks.Count(t => t.Status == TaskStatus.InProgress) + В разработке +

+
+ + @foreach (var task in Model.Tasks.Where(t => t.Status == TaskStatus.InProgress)) + { +
+
+
@task.Title
+ В работе +
+

@(task.Description ?? "Нет описания")

+
+ + @task.Assignee + +
+
+
+ + + + +
+
+
+ } + + @if (Model.Tasks.All(t => t.Status is not TaskStatus.InProgress)) + { +
+ +

Нет задач в разработке

+
+ } +
+
+ +
+
+
+

+ @Model.Tasks.Count(t => t.Status == TaskStatus.InReview) + На ревью +

+
+ + @foreach (var task in Model.Tasks.Where(t => t.Status == TaskStatus.InReview)) + { +
+
+
@task.Title
+ На проверке +
+

@(task.Description ?? "Нет описания")

+
+ + @task.Assignee + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ } + + @if (Model.Tasks.All(t => t.Status != TaskStatus.InReview)) + { +
+ +

Нет задач на ревью

+
+ } +
+
+ +
+
+
+

+ @Model.Tasks.Count(t => t.Status == TaskStatus.Done) + Выполнено +

+
+ + @foreach (var task in Model.Tasks.Where(t => t.Status == TaskStatus.Done)) + { +
+
+
@task.Title
+ Выполнено +
+

@(task.Description ?? "Нет описания")

+
+ + @task.Assignee + +
+
+ + + + +
+
+ } + + @if (Model.Tasks.All(t => t.Status != TaskStatus.Done)) + { +
+ +

Нет выполненных задач

+
+ } +
+
+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Index.cshtml b/SMMTracker.WebUI/Pages/Index.cshtml new file mode 100644 index 0000000..9552b7f --- /dev/null +++ b/SMMTracker.WebUI/Pages/Index.cshtml @@ -0,0 +1,19 @@ +@page +@{ + ViewData["Title"] = "SMM Tracker"; + Layout = "_Layout"; +} + +
+
+
+
+

SMM Tracker

+

Добро пожаловать!

+ +
+
+
+
\ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Index.razor b/SMMTracker.WebUI/Pages/Index.razor deleted file mode 100644 index c5fb9a1..0000000 --- a/SMMTracker.WebUI/Pages/Index.razor +++ /dev/null @@ -1,102 +0,0 @@ -@* @page "/" *@ -@* @using BlazorApp1.Services *@ -@* @inject UserStateService UserStateService *@ -@* @inject NavigationManager NavigationManager *@ -@* *@ -@* Главная страница *@ -@* *@ -@* @if (UserStateService.CurrentUser != null) *@ -@* { *@ -@* $1$ показывается, если пользователь вошел в систему #1# *@ -@*
*@ -@*
*@ -@*

Добро пожаловать, @UserStateService.CurrentUser.UserName!

*@ -@*
*@ -@*
*@ -@*

Вы успешно вошли в систему.

*@ -@*
    *@ -@*
  • *@ -@* Ваш Telegram ID: @UserStateService.CurrentUser.TelegramId *@ -@*
  • *@ -@*
  • *@ -@* Ваш уникальный код: @UserStateService.CurrentUser.UniqueCode *@ -@*
  • *@ -@*
*@ -@*
*@ -@* *@ -@*
*@ -@* } *@ -@* else *@ -@* { *@ -@* $1$ показывается, если пользователь не вошел в систему #1# *@ -@* *@ -@* } *@ -@* *@ -@* @code { *@ -@* // при нажатии на выйти *@ -@* private void Logout() *@ -@* { *@ -@* UserStateService.LogOut(); *@ -@* NavigationManager.NavigateTo("/login"); *@ -@* } *@ -@* } *@ - -@page "/" -@using SMMTracker.Infrastructure.Services -@inject UserStateService UserStateService -@inject NavigationManager NavigationManager - -Главная страница - -@if (UserStateService.CurrentUser != null) -{ - @* показывается, если пользователь вошел в систему *@ -
-
-

Добро пожаловать, @UserStateService.CurrentUser.TelegramUsername!

-
-
-

Вы успешно вошли в систему.

-
    -
  • - ID пользователя: @UserStateService.CurrentUser.Id -
  • -
  • - Telegram ID: @UserStateService.CurrentUser.TelegramId -
  • -
  • - Имя: @UserStateService.CurrentUser.FirstName -
  • -
  • - Фамилия: @UserStateService.CurrentUser.LastName -
  • -
-
- -
-} -else -{ - @* показывается, если пользователь не вошел в систему *@ - -} - -@code { - private void Logout() - { - UserStateService.LogOut(); - NavigationManager.NavigateTo("/login"); - } -} diff --git a/SMMTracker.WebUI/Pages/Login.cshtml b/SMMTracker.WebUI/Pages/Login.cshtml new file mode 100644 index 0000000..34e2424 --- /dev/null +++ b/SMMTracker.WebUI/Pages/Login.cshtml @@ -0,0 +1,86 @@ +@page +@model SMMTracker.WebUI.ViewModels.LoginModel +@using Microsoft.AspNetCore.Authorization +@attribute [AllowAnonymous] +@{ + ViewData["Title"] = "Вход"; + Layout = "_Layout"; +} + +@if (Model.IsAuthenticated) +{ +
+
+
+
+

Вы уже авторизованы

+

Вы вошли как: @User.Identity?.Name

+
+ + Перейти в Dashboard + +
+ +
+
+ Если это не вы, нажмите "Принудительный выход" +
+
+
+
+
+
+} +else +{ +
+
+
+
+
+

+ +
+ Telegram Авторизация +

+

Войдите с помощью вашего Telegram аккаунта

+
+ + +
+ +
+ +
+

+ Нажмите кнопку выше, чтобы авторизоваться через Telegram +

+
+ + + + + @if (!string.IsNullOrEmpty(Model.ErrorMessage)) + { + + } +
+
+
+
+} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Login.razor b/SMMTracker.WebUI/Pages/Login.razor deleted file mode 100644 index 74aff77..0000000 --- a/SMMTracker.WebUI/Pages/Login.razor +++ /dev/null @@ -1,136 +0,0 @@ -@page "/login" - -@using System.Security.Cryptography; -@using System.Text; -@using SMMTracker.Domain.Entities -@using Microsoft.AspNetCore.Identity -@using Task = System.Threading.Tasks.Task -@using User = Telegram.Bot.Types.User -@using BlazorApp1.DTOs; -@using SMMTracker.Application.Dtos - -@* // Для вызова JavaScript *@ -@inject IJSRuntime JSRuntime -@* // Для работы с пользователями в БД *@ -@inject SMMTracker.Infrastructure.Services.UserManager UserManager -@* // Для сохранения состояния пользователя *@ -@inject SMMTracker.Infrastructure.Services.UserStateService UserStateService -@* // Для перенаправления на другие страницы *@ -@inject NavigationManager NavigationManager -@* // Для чтения настроек (например, токена) *@ -@inject IConfiguration Configuration - -@* // метод для отрисовки кнопки для входа через тг *@ - -

Вход в систему

-

Пожалуйста, войдите, используя ваш аккаунт Telegram.

- -@* встраиваем кнопку для тг *@ -
- -@if (!string.IsNullOrEmpty(errorMessage)) -{ - -} - -@code -{ - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - var reference = DotNetObjectReference.Create(this); - await JSRuntime.InvokeVoidAsync("telegramLogin.renderWidget", - "telegram-login-widget", reference); - } - } -} - -@code -{ - private string? errorMessage; - - [JSInvokable] -//<<<<<<< HEAD:BlazorApp1/Pages/Login.razor - public async Task ProcessTelegramAuth(TelegramLoginData data) - { - using var http = new HttpClient { BaseAddress = new Uri(NavigationManager.BaseUri) }; - var response = await http.PostAsJsonAsync("api/auth/telegram", data); - - if (!response.IsSuccessStatusCode) - { - errorMessage = "Ошибка при авторизации на сервере."; - StateHasChanged(); - return; - } - - var appUserEntity = await response.Content.ReadFromJsonAsync(); - var appUserDto = new UserDto - { - Id = appUserEntity.Id, - FirstName = appUserEntity.FirstName, - LastName = appUserEntity.LastName, - TelegramId = appUserEntity.TelegramId, - TelegramUsername = appUserEntity.TelegramUsername - }; - UserStateService.SetUser(appUserDto); - NavigationManager.NavigateTo("/"); - } -// -// private bool IsTelegramDataValid(Data.models.User user) -// ======= -// public async Task ProcessTelegramAuth(Domain.Entities.User user) -// { -// errorMessage = null; -// try -// { -// if (!IsTelegramDataValid(user)) -// { -// errorMessage = "Ошибка проверки данных. Пожалуйста, попробуйте войти снова"; -// StateHasChanged(); // чтобы показать страницу с ошибкой -// return; -// } -// -// var appUser = await UserManager.FindOrCreateUserAsync(user.TelegramId, user.FirstName, user.LastName,user.TelegramUsername); -// UserStateService.SetUser(appUser); -// NavigationManager.NavigateTo("/"); -// } -// catch (Exception e) -// { -// Console.WriteLine(e); -// errorMessage = "Произошла непредвиденная ошибка на сервере. Попробуйте позже."; -// StateHasChanged(); -// } -// } - - private bool IsTelegramDataValid(Domain.Entities.User user) - { - var botToken = Configuration["TelegramBotToken"]; - if (string.IsNullOrEmpty(botToken)) - throw new InvalidOperationException("Bot not founded!"); - - var userData = new List(); - var allProperties = user.GetType().GetProperties(); - foreach (var property in allProperties) - { - if (property.Name == "hash") continue; - var value = property.GetValue(user); - if (value != null) - userData.Add($"{property.Name}={value}"); - } - - userData.Sort(); - var userDataString = string.Join("\n", userData); - - using var sha256 = SHA256.Create(); - var tokenBytes = Encoding.UTF8.GetBytes(botToken); - var secretKey = SHA256.HashData(tokenBytes); - using var hmac = new HMACSHA256(secretKey); - var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(userDataString)); - var computedHashString = BitConverter.ToString(computedHash).Replace("-", "").ToLower(); - - return user.Hash == computedHashString; - } -} diff --git a/SMMTracker.WebUI/Pages/Logout.cshtml b/SMMTracker.WebUI/Pages/Logout.cshtml new file mode 100644 index 0000000..385f1cf --- /dev/null +++ b/SMMTracker.WebUI/Pages/Logout.cshtml @@ -0,0 +1,19 @@ +@page +@model SMMTracker.WebUI.ViewModels.LogoutModel +@using Microsoft.AspNetCore.Authorization +@attribute [AllowAnonymous] +@{ + ViewData["Title"] = "Выход"; + Layout = "_Layout"; +} + + + + + Выход... + + + +

Выход из системы...

+ + \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/Team.cshtml b/SMMTracker.WebUI/Pages/Team.cshtml new file mode 100644 index 0000000..3c8d200 --- /dev/null +++ b/SMMTracker.WebUI/Pages/Team.cshtml @@ -0,0 +1,669 @@ +@page "/Team/{id}" +@model SMMTracker.WebUI.ViewModels.TeamModel +@using SMMTracker.Application.Abstractions +@inject IAchievementService AchievementService + +@{ + ViewData["Title"] = Model.Team.Name; + Layout = "_Layout"; + + // Логика проверки ачивок + var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (int.TryParse(userIdClaim, out var userId)) + { + // Используем .GetAwaiter().GetResult(), так как мы находимся внутри синхронного блока Razor + var newAchievements = AchievementService.CheckAchievementsAsync(userId).GetAwaiter().GetResult(); + + if (newAchievements != null && newAchievements.Any()) + { + TempData["NewAchievement"] = newAchievements.First().Title; + } + } +} +@{ + ViewData["Title"] = Model.Team.Name; + Layout = "_Layout"; +} + + + +
+ + + @if (TempData["SuccessMessage"] != null) + { +
+ +
+ } + + @if (TempData["ErrorMessage"] != null) + { +
+ +
+ } + +
+
+ + +
+
+

@Model.Team.Name

+

@Model.Team.Description

+
+ +
+
+

Информация о команде

+
+
+
Описание команды
+

@Model.Team.Description

+
+
+
+
+
Детали команды
+
+
+
Участников@Model.Team.MemberCount
+
+
+
Дата создания@Model.Team.CreatedAt.ToString("dd.MM.yyyy")
+
+
+
+ Код приглашения +
+ @Model.Team.InvitationCode + +
+
+
+
+
+
+
Быстрые действия
+
+ + @if (Model.IsOwner) + { + + } +
+
+
+
+
+ +
+
+

Участники команды

+ @Model.Members.Count участников +
+
+ @foreach (var member in Model.Members) + { +
+
+
+
+
+ +
+
+
+
@member.FirstName @member.LastName
+

@@@member.TelegramUsername

+
+ @member.Role + @member.JoinedAt.ToString("dd.MM.yyyy") +
+
+
+ @if (Model.IsOwner && member.Role != "Владелец") + { +
+ + + +
+ } +
+
+ } +
+
+ +
+
+

Предстоящие + мероприятия

+ @Model.UpcomingEvents.Count мероприятий +
+ + + @if (!Model.UpcomingEvents.Any()) + { +
+
+ +
Нет мероприятий
+

Добавьте первое мероприятие для вашей команды.

+
+
+ } +
+ +
+
+
+

Календарь команды (в разработке...)

+
+
+ +

+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ + + + + + + +} \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/_Host.cshtml b/SMMTracker.WebUI/Pages/_Host.cshtml deleted file mode 100644 index 6066096..0000000 --- a/SMMTracker.WebUI/Pages/_Host.cshtml +++ /dev/null @@ -1,29 +0,0 @@ -@page "/" -@namespace SMMTracker.WebUI.Shared -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@{ - Layout = "_Layout"; -} - - - - - - diff --git a/SMMTracker.WebUI/Pages/_Layout.cshtml b/SMMTracker.WebUI/Pages/_Layout.cshtml index 97a49b7..afc6455 100644 --- a/SMMTracker.WebUI/Pages/_Layout.cshtml +++ b/SMMTracker.WebUI/Pages/_Layout.cshtml @@ -1,36 +1,200 @@ -@using Microsoft.AspNetCore.Components.Web -@namespace SMMTracker.WebUI.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers - - - + + + + + + + + - - SMMTracker - - - - - - + @ViewData["Title"] - SMM Tracker + + + -@RenderBody() -
- - Произошла ошибка. Приложение может не отвечать до перезагрузки. - - - Произошла необработанная ошибка. Подробности в инструментах разработчика. - - Перезагрузить - 🗙 +
+ +
+ +
+
+ @RenderBody() +
- - + +@await RenderSectionAsync("Scripts", required: false) + + + + \ No newline at end of file diff --git a/SMMTracker.WebUI/Pages/_ViewImports.cshtml b/SMMTracker.WebUI/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..55c622b Binary files /dev/null and b/SMMTracker.WebUI/Pages/_ViewImports.cshtml differ diff --git a/SMMTracker.WebUI/Pages/_ViewStart.cshtml b/SMMTracker.WebUI/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..b20c7df Binary files /dev/null and b/SMMTracker.WebUI/Pages/_ViewStart.cshtml differ diff --git a/SMMTracker.WebUI/Program.cs b/SMMTracker.WebUI/Program.cs index da32d4f..c0e4aca 100644 --- a/SMMTracker.WebUI/Program.cs +++ b/SMMTracker.WebUI/Program.cs @@ -1,73 +1,139 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Components; using Microsoft.EntityFrameworkCore; +using SMMTracker.Application.Abstractions; using SMMTracker.Infrastructure.Data.DataContext; -using SMMTracker.Infrastructure.Services; -using SMMTracker.Application.Interfaces; +using SMMTracker.Application.Services; +using SMMTracker.Domain.IRepositories; +using SMMTracker.Infrastructure.Repositories; using SMMTracker.Application.Services; -var builder = WebApplication.CreateBuilder(args); - -// Настройка HTTP -builder.WebHost.UseUrls("http://localhost:5002", "http://0.0.0.0:5002"); - -// Базовые сервисы ASP.NET Core -builder.Services.AddRazorPages(); -builder.Services.AddServerSideBlazor(); -builder.Services.AddControllers(); - -// База данных -// var dbDir = Path.Combine(AppContext.BaseDirectory, "SharedDatabase"); -// Directory.CreateDirectory(dbDir); -// var dbPath = Path.Combine(dbDir, "DataBase.db"); -// var connectionString = $"Data Source={dbPath}"; - -// builder.Services.AddDbContext(opt => -// opt.UseSqlite(connectionString)); -var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); - -builder.Services.AddDbContext(opt => - opt.UseSqlite(connectionString)); - -// Сервисы приложения -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(provider => - provider.GetRequiredService()); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddEndpointsApiExplorer(); // Эта строка нужна для Swagger -builder.Services.AddSwaggerGen(); // А эта его добавляет - -var app = builder.Build(); +namespace SMMTracker.WebUI; -// Применяем миграции -using (var scope = app.Services.CreateScope()) +public static class Program +{ + public static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Configuration + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile("appsettings.Secrets.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + ConfigureServices(builder); + + var app = builder.Build(); + await ConfigureMiddlewareAsync(app); + + await app.RunAsync(); + } + + private static void ConfigureServices(WebApplicationBuilder builder) + { + var services = builder.Services; + var config = builder.Configuration; + + var connectionString = config.GetConnectionString("DefaultConnection"); + services.AddDbContext(opt => opt.UseSqlite(connectionString)); + services.AddScoped(sp => sp.GetRequiredService()); + + services.AddRazorPages(); + services.AddControllers(); // Это у тебя уже есть + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddHttpClient(); + + // Репозитории + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Сервисы + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // --- ВОТ ТА САМАЯ СТРОКА РЕГИСТРАЦИИ АЧИВОК --- + services.AddScoped(); + // ---------------------------------------------- + + services.AddScoped(_ => + new HttpClient { BaseAddress = new Uri("https://smmtracker.ru/") }); + + services.AddCors(options => options.AddPolicy("CorsPolicy", + policy => policy.WithOrigins("https://smmtracker.ru/") + .AllowAnyHeader() + .AllowAnyMethod())); + + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.LoginPath = "/Login"; + options.AccessDeniedPath = "/Login"; + options.ExpireTimeSpan = TimeSpan.FromDays(30); + options.SlidingExpiration = true; + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Lax; + + options.Events.OnRedirectToLogin = context => + { + if (context.Request.Path.StartsWithSegments("/api")) + { + context.Response.StatusCode = 401; + return Task.CompletedTask; + } + context.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + options.Events.OnRedirectToAccessDenied = context => + { + if (context.Request.Path.StartsWithSegments("/api")) + { + context.Response.StatusCode = 403; + return Task.CompletedTask; + } + context.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + }); + + services.AddAuthorization(); + } + + private static async Task ConfigureMiddlewareAsync(WebApplication app) { + using var scope = app.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); await context.Database.MigrateAsync(); + + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(); + } + else + { + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + + app.UseStaticFiles(); + app.UseRouting(); + app.UseCors("CorsPolicy"); + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapRazorPages(); + app.MapControllers(); } - -// Middleware -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error"); - app.UseHsts(); -} -else -{ - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(); // Эта строка добавляет веб-интерфейс -} - -app.UseStaticFiles(); -app.UseRouting(); - -app.MapBlazorHub(); -app.MapFallbackToPage("/_Host"); -app.MapControllers(); - -Console.WriteLine("Web application started: [http://localhost:5002](http://localhost:5002)"); - -app.Run(); \ No newline at end of file +} \ No newline at end of file diff --git a/SMMTracker.WebUI/SMMTracker.WebUI.csproj b/SMMTracker.WebUI/SMMTracker.WebUI.csproj index 0fd84ab..9501b53 100644 --- a/SMMTracker.WebUI/SMMTracker.WebUI.csproj +++ b/SMMTracker.WebUI/SMMTracker.WebUI.csproj @@ -1,6 +1,7 @@ + false net8.0 enable enable @@ -8,8 +9,6 @@ - - @@ -19,4 +18,23 @@ + + <_ContentIncludedByDefault Remove="Shared\MainLayout.razor" /> + <_ContentIncludedByDefault Remove="Shared\NavMenu.razor" /> + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" /> + <_ContentIncludedByDefault Remove="wwwroot\css\site.css" /> + <_ContentIncludedByDefault Remove="wwwroot\favicon.ico" /> + <_ContentIncludedByDefault Remove="wwwroot\js\telegramLogin.js" /> + + \ No newline at end of file diff --git a/SMMTracker.WebUI/Services/UserStateService.cs b/SMMTracker.WebUI/Services/UserStateService.cs deleted file mode 100644 index b26ac35..0000000 --- a/SMMTracker.WebUI/Services/UserStateService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using SMMTracker.Domain.Entities; -namespace BlazorApp1.Services; - -public class UserStateService -{ - private User? _currentUser; - - public User? CurrentUser => _currentUser; - - public void SetUser(User user) - { - _currentUser = user; - NotifyStateChanged(); - } - - public void LogOut() - { - _currentUser = null; - NotifyStateChanged(); - } - - public event Action? OnStateChange; - - private void NotifyStateChanged() => OnStateChange?.Invoke(); -} \ No newline at end of file diff --git a/SMMTracker.WebUI/Shared/MainLayout.razor b/SMMTracker.WebUI/Shared/MainLayout.razor deleted file mode 100644 index dad920a..0000000 --- a/SMMTracker.WebUI/Shared/MainLayout.razor +++ /dev/null @@ -1,19 +0,0 @@ -@inherits LayoutComponentBase - -SMMTracker - -
- - -
-
- About -
- -
- @Body -
-
-
\ No newline at end of file diff --git a/SMMTracker.WebUI/Shared/MainLayout.razor.css b/SMMTracker.WebUI/Shared/MainLayout.razor.css deleted file mode 100644 index 551e4b2..0000000 --- a/SMMTracker.WebUI/Shared/MainLayout.razor.css +++ /dev/null @@ -1,70 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - } - - .top-row a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row a, .top-row .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/SMMTracker.WebUI/Shared/NavMenu.razor b/SMMTracker.WebUI/Shared/NavMenu.razor deleted file mode 100644 index 1984248..0000000 --- a/SMMTracker.WebUI/Shared/NavMenu.razor +++ /dev/null @@ -1,49 +0,0 @@ - - - - -@code { - private bool collapseNavMenu = true; - - private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} \ No newline at end of file diff --git a/SMMTracker.WebUI/Shared/NavMenu.razor.css b/SMMTracker.WebUI/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f..0000000 --- a/SMMTracker.WebUI/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/SMMTracker.WebUI/ViewModels/CreateTeamViewModel.cs b/SMMTracker.WebUI/ViewModels/CreateTeamViewModel.cs new file mode 100644 index 0000000..d726971 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/CreateTeamViewModel.cs @@ -0,0 +1,7 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class CreateTeamViewModel +{ + public string Name { get; set; } = ""; + public string Description { get; set; } = ""; +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/DashboardModel.cs b/SMMTracker.WebUI/ViewModels/DashboardModel.cs new file mode 100644 index 0000000..f78ab2f --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/DashboardModel.cs @@ -0,0 +1,175 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using SMMTracker.Application.Abstractions; +using System.Security.Claims; +using SMMTracker.Application.Dtos; + +namespace SMMTracker.WebUI.ViewModels; + +public class DashboardModel : PageModel +{ + private readonly IUserService _userService; + private readonly ITeamService _teamService; + + public DashboardModel(IUserService userService, ITeamService teamService) + { + _userService = userService; + _teamService = teamService; + } + + public DashboardViewModel ViewModel { get; set; } = new(); + + [BindProperty] public EditProfileViewModel EditProfile { get; set; } = new(); + + [BindProperty] public CreateTeamViewModel NewTeam { get; set; } = new(); + + [BindProperty] public string InvitationCodeInput { get; set; } = ""; + + public bool ShowProfileModal { get; set; } + public bool ShowTeamModal { get; set; } + + public async Task OnGetAsync() + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return RedirectToPage("/Login"); + } + + var userDto = await _userService.GetUserByIdAsync(userId); + if (userDto == null) + { + return RedirectToPage("/Logout"); + } + + var teams = await _teamService.GetTeamsForUserAsync(userId); + + ViewModel.UserInfo = new UserInfoViewModel + { + Id = userDto.Id, + TelegramId = userDto.TelegramId, + FirstName = userDto.FirstName, + LastName = userDto.LastName, + TelegramUsername = userDto.UserName, + ProfileDescription = userDto.ProfileDescription ?? "" + }; + + ViewModel.Teams = teams.Select(t => new TeamViewModel + { + Id = t.Id, + Name = t.Name, + InvitationCode = t.InvitationCode, + CreatedAt = t.CreatedAt, + MemberCount = t.MemberCount, + IsOwner = t.IsOwner + }).ToList(); + + return Page(); + } + + public async Task OnPostJoinTeamAsync() + { + if (string.IsNullOrWhiteSpace(InvitationCodeInput)) + { + TempData["ErrorMessage"] = "Введите код приглашения"; + return RedirectToPage(); + } + + try + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return Unauthorized(); + } + + var joinDto = new JoinTeamDto + { + Code = InvitationCodeInput, + UserId = userId + }; + + var success = await _teamService.JoinTeamAsync(joinDto); + + if (success) + { + TempData["SuccessMessage"] = "Вы успешно присоединились к команде!"; + } + else + { + TempData["ErrorMessage"] = "Неверный код приглашения или вы уже состоите в этой команде"; + } + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Произошла ошибка: {ex.Message}"; + } + + return RedirectToPage(); + } + + public async Task OnPostLeaveTeamAsync(int teamId) + { + var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + + try + { + await _teamService.LeaveTeamAsync(teamId, userId); + TempData["SuccessMessage"] = "Вы покинули команду"; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(); + } + + public async Task OnPostUpdateProfileAsync() + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return Unauthorized(); + } + + if (!ModelState.IsValid) + { + await OnGetAsync(); + EditProfile.FirstName = ViewModel.UserInfo.FirstName; + EditProfile.LastName = ViewModel.UserInfo.LastName; + EditProfile.ProfileDescription = ViewModel.UserInfo.ProfileDescription; + ShowProfileModal = true; + return Page(); + } + + var profileDto = new UserProfileDto + { + FirstName = EditProfile.FirstName, + LastName = EditProfile.LastName, + Description = EditProfile.ProfileDescription + }; + + try + { + await _userService.UpdateUserProfileAsync(userId, profileDto); + TempData["SuccessMessage"] = "Профиль успешно обновлен!"; + TempData["ShowProfileModal"] = false; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка при обновлении профиля: {ex.Message}"; + TempData["ShowProfileModal"] = true; + } + + return RedirectToPage(); + } + + private string GenerateInvitationCode() + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + return new string(Enumerable.Repeat(chars, 6) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/DashboardViewModel.cs b/SMMTracker.WebUI/ViewModels/DashboardViewModel.cs new file mode 100644 index 0000000..75ff476 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/DashboardViewModel.cs @@ -0,0 +1,7 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class DashboardViewModel +{ + public UserInfoViewModel UserInfo { get; set; } = new UserInfoViewModel(); + public List Teams { get; set; } = new List(); +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/EditProfileViewModel.cs b/SMMTracker.WebUI/ViewModels/EditProfileViewModel.cs new file mode 100644 index 0000000..c7de7a2 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/EditProfileViewModel.cs @@ -0,0 +1,8 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class EditProfileViewModel +{ + public string FirstName { get; set; } = ""; + public string LastName { get; set; } = ""; + public string ProfileDescription { get; set; } = ""; +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/EventModel.cs b/SMMTracker.WebUI/ViewModels/EventModel.cs new file mode 100644 index 0000000..765f4d3 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/EventModel.cs @@ -0,0 +1,206 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Security.Claims; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; +using TaskStatus = SMMTracker.Domain.Enums.TaskStatus; + +namespace SMMTracker.WebUI.ViewModels; + +public class EventModel : PageModel +{ + [BindProperty] public NewTaskViewModel NewTask { get; set; } = new(); + + [BindProperty] public string CommentText { get; set; } = ""; + public TeamViewModel Team { get; set; } = new(); + public List TeamMembers { get; set; } = new(); + public EventViewModel Event { get; set; } = new(); + + public List Tasks { get; set; } = new(); + + private readonly IEventService _eventService; + private readonly ITaskService _taskService; + private readonly ITeamService _teamService; + + public EventModel(IEventService eventService, ITaskService taskService, ITeamService teamService) + { + _eventService = eventService; + _taskService = taskService; + _teamService = teamService; + } + + public async Task OnGetAsync(int eventId) + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return RedirectToPage("/Login"); + } + + var eventDetails = await _eventService.GetEventDetailsAsync(eventId); + if (eventDetails == null) + { + return NotFound("Событие не найдено"); + } + + Event = new EventViewModel + { + Id = eventDetails.Id, + Title = eventDetails.Name, + Description = eventDetails.Description ?? "", + EventDate = eventDetails.Date, + }; + + TeamMembers = (await _teamService.GetTeamMembersAsync(eventDetails.TeamId)) + .Select(u => new TeamMemberViewModel + { + Id = u.UserId, + FirstName = u.FirstName, + LastName = u.LastName, + }).ToList(); + + Tasks = eventDetails.Tasks.Select(t => new EventTaskViewModel + { + Id = t.Id, + Title = t.Name, + Description = t.Description, + Status = (TaskStatus)t.Status, + }).ToList(); + + return Page(); + } + + public async Task OnPostMoveToProgressFromReviewAsync(int eventId, int taskId) + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return RedirectToPage("/Login"); + } + + try + { + await _taskService.MoveTaskToProgressAsync(taskId); + TempData["SuccessMessage"] = "Задача возвращена в работу"; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(new { eventId }); + } + + public async Task OnPostAddTaskAsync(int eventId) + { + if (string.IsNullOrWhiteSpace(NewTask.Title)) + { + TempData["ErrorMessage"] = "Название задачи не может быть пустым."; + return await OnGetAsync(eventId); + } + + var eventDetails = await _eventService.GetEventDetailsAsync(eventId); + if (eventDetails == null) return NotFound(); + + var createTaskDto = new CreateTaskDto + { + Name = NewTask.Title, + Description = NewTask.Description, + EventId = eventId, + }; + + await _taskService.CreateTaskAsync(createTaskDto); + + return RedirectToPage(new { eventId }); + } + + public async Task OnPostMoveToReviewAsync(int eventId, int taskId) + { + await _taskService.MoveTaskToReviewAsync(taskId); + TempData["SuccessMessage"] = "Задача отправлена на ревью"; + return RedirectToPage(new { eventId }); + } + + public async Task OnPostMoveToDoneAsync(int eventId, int taskId) + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return RedirectToPage("/Login"); + } + + try + { + await _taskService.MoveTaskToDoneAsync(taskId); + TempData["SuccessMessage"] = "Задача выполнена"; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(new { eventId }); + } + + public async Task OnPostRejectTaskAsync(int eventId, int taskId) + { + await _taskService.MoveTaskToProgressAsync(taskId); + TempData["SuccessMessage"] = "Задача возвращена в разработку"; + return RedirectToPage(new { eventId }); + } + + public async Task OnPostDeleteTaskAsync(int eventId, int taskId) + { + try + { + await _taskService.DeleteTaskAsync(taskId); + TempData["SuccessMessage"] = "Задача удалена."; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(new { eventId }); + } + + public async Task OnPostAddCommentAsync(int calendarId, int eventId) + { + if (string.IsNullOrWhiteSpace(CommentText)) + { + TempData["ErrorMessage"] = "Введите текст комментария"; + return await OnGetAsync(eventId); + } + + TempData["SuccessMessage"] = "Комментарий добавлен."; + return RedirectToPage(new { eventId }); + } +} + +public class EventViewModel +{ + public int Id { get; set; } + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; + public DateTime EventDate { get; set; } +} + +public class EventTaskViewModel +{ + public int Id { get; set; } + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; + public TaskStatus Status { get; set; } + public string CreatedBy { get; set; } = ""; + public DateTime CreatedAt { get; set; } + public string Assignee { get; set; } = ""; + public DateTime? MovedToReviewAt { get; set; } + public DateTime? CompletedAt { get; set; } +} + +public class NewTaskViewModel +{ + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; + public int Assignee { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/LoginModel.cs b/SMMTracker.WebUI/ViewModels/LoginModel.cs new file mode 100644 index 0000000..323de50 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/LoginModel.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Configuration; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; + +namespace SMMTracker.WebUI.ViewModels; + +public class LoginModel : PageModel +{ + public string BotName { get; set; } = ""; + public string CallbackUrl { get; set; } = ""; + public string ErrorMessage { get; set; } = ""; + public bool IsAuthenticated { get; set; } + + private readonly IConfiguration _configuration; + + public LoginModel(IConfiguration configuration) + { + _configuration = configuration; + } + + public IActionResult OnGet(string? error = null) + { + IsAuthenticated = User.Identity?.IsAuthenticated == true && + User.HasClaim(c => c.Type == ClaimTypes.NameIdentifier); + + if (IsAuthenticated) + { + Console.WriteLine($"[LOGIN_DEBUG] Пользователь авторизован: {User.Identity?.Name}"); + Console.WriteLine( + $"[LOGIN_DEBUG] Claims: {string.Join(", ", User.Claims.Select(c => $"{c.Type}:{c.Value}"))}"); + } + else + { + Console.WriteLine($"[LOGIN_DEBUG] Пользователь НЕ авторизован"); + } + + if (!string.IsNullOrEmpty(error)) + { + ErrorMessage = error switch + { + "auth_failed" => "Ошибка авторизации. Попробуйте снова.", + "invalid_data" => "Неверные данные авторизации.", + "timeout" => "Время авторизации истекло. Попробуйте снова.", + _ => "Произошла ошибка при авторизации." + }; + } + + BotName = _configuration["Telegram:BotName"] ?? "SmmTrackerTestBot_bot"; + + CallbackUrl = "https://smmtracker.ru//api/auth/telegram-callback"; + + return Page(); + } + + public async Task OnPostForceLogout() + { + await HttpContext.SignOutAsync(); + return RedirectToPage("/Login"); + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/LogoutModel.cs b/SMMTracker.WebUI/ViewModels/LogoutModel.cs new file mode 100644 index 0000000..32bbefb --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/LogoutModel.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace SMMTracker.WebUI.ViewModels; + +public class LogoutModel : PageModel +{ + public async Task OnGetAsync() + { + await HttpContext.SignOutAsync(); + return RedirectToPage("/Login"); + } + + public async Task OnPostAsync() + { + await HttpContext.SignOutAsync(); + return RedirectToPage("/Login"); + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/NewEventViewModel.cs b/SMMTracker.WebUI/ViewModels/NewEventViewModel.cs new file mode 100644 index 0000000..98d4e81 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/NewEventViewModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace SMMTracker.WebUI.ViewModels; + +public class NewEventViewModel +{ + [Required(ErrorMessage = "Введите название мероприятия")] + public string Title { get; set; } = ""; + + public string Description { get; set; } = ""; + + [Required(ErrorMessage = "Укажите дату и время мероприятия")] + public DateTime EventDate { get; set; } = DateTime.Now.AddDays(1); +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/TeamEventViewModel.cs b/SMMTracker.WebUI/ViewModels/TeamEventViewModel.cs new file mode 100644 index 0000000..739cf96 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/TeamEventViewModel.cs @@ -0,0 +1,11 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class TeamEventViewModel +{ + public int Id { get; set; } + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; + public DateTime EventDate { get; set; } + public DateTime CreatedAt { get; set; } + public int CreatedBy { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/TeamMemberViewModel.cs b/SMMTracker.WebUI/ViewModels/TeamMemberViewModel.cs new file mode 100644 index 0000000..df9b5a9 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/TeamMemberViewModel.cs @@ -0,0 +1,12 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class TeamMemberViewModel +{ + public int Id { get; set; } + public long TelegramId { get; set; } + public string FirstName { get; set; } = ""; + public string LastName { get; set; } = ""; + public string TelegramUsername { get; set; } = ""; + public string Role { get; set; } = ""; + public DateTime JoinedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/TeamModel.cs b/SMMTracker.WebUI/ViewModels/TeamModel.cs new file mode 100644 index 0000000..be40706 --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/TeamModel.cs @@ -0,0 +1,174 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using SMMTracker.Application.Abstractions; +using SMMTracker.Application.Dtos; +using SMMTracker.WebUI.ViewModels; +using System.Security.Claims; + +namespace SMMTracker.WebUI.ViewModels; + +public class TeamModel : PageModel +{ + private readonly ITeamService _teamService; + private readonly IEventService _eventService; + private readonly IUserService _userService; + + public TeamViewModel Team { get; set; } = new(); + public List Members { get; set; } = new(); + public List UpcomingEvents { get; set; } = new(); + public bool IsOwner { get; set; } + public int CalendarId { get; set; } + + [BindProperty] public string? NewMemberUsername { get; set; } + [BindProperty] public NewEventViewModel NewEvent { get; set; } = new(); + + [BindProperty] public string TeamName { get; set; } = ""; + [BindProperty] public string TeamDescription { get; set; } = ""; + [BindProperty] public int TeamId { get; set; } + + public TeamModel(ITeamService teamService, IEventService eventService, IUserService userService) + { + _teamService = teamService; + _eventService = eventService; + _userService = userService; + } + + public async Task OnGetAsync(int id) + { + var userIdString = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!int.TryParse(userIdString, out var userId)) + { + return RedirectToPage("/Login"); + } + + var teamDetails = await _teamService.GetTeamDetailsAsync(id); + if (teamDetails == null) + { + return NotFound("Команда не найдена"); + } + + IsOwner = await _teamService.IsUserAdminAsync(id, userId); + + Team = new TeamViewModel + { + Id = teamDetails.Id, + Name = teamDetails.Name, + InvitationCode = teamDetails.InvitationCode, + MemberCount = teamDetails.Members.Count, + Description = teamDetails.Description, + IsOwner = IsOwner + }; + + TeamId = Team.Id; + TeamName = Team.Name; + TeamDescription = Team.Description; + + Members = teamDetails.Members.Select(m => new TeamMemberViewModel + { + Id = m.UserId, + FirstName = m.FirstName, + LastName = m.LastName, + TelegramUsername = m.Username, + Role = m.Role.ToString() + }).ToList(); + + var eventDtos = await _eventService.GetEventsForTeamAsync(id); + UpcomingEvents = eventDtos + .Where(e => e.Date.ToLocalTime().Date >= DateTime.Today) + .OrderBy(e => e.Date) + .Select(e => new TeamEventViewModel + { + Id = e.Id, + Title = e.Name, + Description = e.Description, + EventDate = e.Date, + CreatedAt = e.CreatedAt, + CreatedBy = e.CreatedBy + }).ToList(); + + return Page(); + } + + public async Task OnPostEditTeamAsync() + { + var adminId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + + if (!await _teamService.IsUserAdminAsync(TeamId, adminId)) + { + TempData["ErrorMessage"] = "У вас нет прав для редактирования команды."; + return RedirectToPage(new { id = TeamId }); + } + + if (string.IsNullOrWhiteSpace(TeamName)) + { + TempData["ErrorMessage"] = "Название команды не может быть пустым"; + return Page(); + } + + try + { + await _teamService.UpdateTeamAsync(TeamId, TeamName, TeamDescription, adminId); + TempData["SuccessMessage"] = "Данные команды обновлены"; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(new { id = TeamId }); + } + + public async Task OnPostAddEventAsync(int id) + { + var isFormValid = true; + if (string.IsNullOrWhiteSpace(NewEvent.Title)) + { + TempData["ErrorMessage"] = "Название мероприятия не может быть пустым"; + isFormValid = false; + } + + if (!isFormValid) + { + return await OnGetAsync(id); + } + + var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + + // var calendar = await _teamService.GetCalendarForTeamAsync(id); + // if (calendar == null) + // { + // TempData["ErrorMessage"] = "Календарь для команды не найден."; + // return RedirectToPage(new { id }); + // } + + var createDto = new CreateEventDto + { + Name = NewEvent.Title, + Description = NewEvent.Description, + Date = NewEvent.EventDate, + TeamId = id, + CreatedBy = userId + }; + + await _eventService.CreateEventAsync(createDto); + TempData["SuccessMessage"] = $"Мероприятие '{NewEvent.Title}' добавлено"; + return RedirectToPage(new { id }); + } + + public async Task OnPostRemoveMemberAsync(int id, int memberId) + { + var currentUserId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + + try + { + await _teamService.RemoveUserFromTeamAsync(id, memberId, currentUserId); + TempData["SuccessMessage"] = "Участник удален из команды."; + } + catch (Exception ex) + { + TempData["ErrorMessage"] = $"Ошибка: {ex.Message}"; + } + + return RedirectToPage(new { id }); + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/TeamViewModel.cs b/SMMTracker.WebUI/ViewModels/TeamViewModel.cs new file mode 100644 index 0000000..b05906b --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/TeamViewModel.cs @@ -0,0 +1,12 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class TeamViewModel +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Description { get; set; } = ""; + public string InvitationCode { get; set; } = ""; + public DateTime CreatedAt { get; set; } = DateTime.Now; + public int MemberCount { get; set; } + public bool IsOwner { get; set; } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/ViewModels/UserInfoViewModel.cs b/SMMTracker.WebUI/ViewModels/UserInfoViewModel.cs new file mode 100644 index 0000000..e0b16df --- /dev/null +++ b/SMMTracker.WebUI/ViewModels/UserInfoViewModel.cs @@ -0,0 +1,11 @@ +namespace SMMTracker.WebUI.ViewModels; + +public class UserInfoViewModel +{ + public int Id { get; set; } + public long TelegramId { get; set; } + public string FirstName { get; set; } = ""; + public string LastName { get; set; } = ""; + public string TelegramUsername { get; set; } = ""; + public string ProfileDescription { get; set; } = ""; +} \ No newline at end of file diff --git a/SMMTracker.WebUI/_Imports.razor b/SMMTracker.WebUI/_Imports.razor deleted file mode 100644 index 8d2dbae..0000000 --- a/SMMTracker.WebUI/_Imports.razor +++ /dev/null @@ -1,10 +0,0 @@ -@using System.Net.Http -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.JSInterop -@using SMMTracker -@using SMMTracker.WebUI.Shared \ No newline at end of file diff --git a/SMMTracker.WebUI/appsettings.Secrets.json b/SMMTracker.WebUI/appsettings.Secrets.json new file mode 100644 index 0000000..8536f4b --- /dev/null +++ b/SMMTracker.WebUI/appsettings.Secrets.json @@ -0,0 +1,6 @@ +{ + "Telegram": { + "BotName": "SmmTrackerTestBot_bot", + "BotToken": "7770947912:AAHRHg0PiUe3PrjFatQ_X4THIwUZs5HZUJ0" + } +} \ No newline at end of file diff --git a/SMMTracker.WebUI/appsettings.json b/SMMTracker.WebUI/appsettings.json index e5c2250..75a13f6 100644 --- a/SMMTracker.WebUI/appsettings.json +++ b/SMMTracker.WebUI/appsettings.json @@ -1,13 +1,13 @@ { + "ConnectionStrings": { + "DefaultConnection": "Data Source=app/data/SMMTracker.db" + }, "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*", - "TelegramBotToken": "8450218559:AAGCQdk6hnrtP8aFZpZM-bCc7tCWeKNWaIE", - "ConnectionStrings": { - "DefaultConnection": "Data Source=SMMTracker.db" - } -} + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/SMMTracker.WebUI/wwwroot/js/site.js b/SMMTracker.WebUI/wwwroot/js/site.js new file mode 100644 index 0000000..adec0aa --- /dev/null +++ b/SMMTracker.WebUI/wwwroot/js/site.js @@ -0,0 +1,4 @@ +let currentCalendarId = 0; +let currentMonth; +let currentYear; +let calendarContainer; \ No newline at end of file diff --git a/SMMTracker.WebUI/wwwroot/js/telegramLogin.js b/SMMTracker.WebUI/wwwroot/js/telegramLogin.js index ae95ebd..d27f7d7 100644 --- a/SMMTracker.WebUI/wwwroot/js/telegramLogin.js +++ b/SMMTracker.WebUI/wwwroot/js/telegramLogin.js @@ -1,21 +1,17 @@ window.telegramLogin = { renderWidget: function (elementId, dotNetMethodName) { - // Эта функция будет вызвана Telegram после успешной авторизации window.onTelegramAuth = (user) => { - // Вызываем C# метод, имя которого нам передали из Blazor <<<<<<< HEAD:BlazorApp1/wwwroot/js/telegramLogin.js DotNet.invokeMethodAsync('BlazorApp1', dotNetMethodName, user); ======= DotNet.invokeMethodAsync('SMMTracker.WebUI', dotNetMethodName, user); >>>>>>> masha:src/SMMTracker.WebUI/wwwroot/js/telegramLogin.js }; - - // Создаем и добавляем на страницу тег - - - \ No newline at end of file diff --git a/src/SMMTracker.WebUI/Services/UserStateService.cs b/src/SMMTracker.WebUI/Services/UserStateService.cs deleted file mode 100644 index b9b7f1f..0000000 --- a/src/SMMTracker.WebUI/Services/UserStateService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Data.models; - -namespace BlazorApp1.Services; - -public class UserStateService -{ - private User? _currentUser; - - public User? CurrentUser => _currentUser; - - public void SetUser(User user) - { - _currentUser = user; - NotifyStateChanged(); - } - - public void LogOut() - { - _currentUser = null; - NotifyStateChanged(); - } - - public event Action? OnStateChange; - - private void NotifyStateChanged() => OnStateChange?.Invoke(); -} \ No newline at end of file diff --git a/src/SMMTracker.WebUI/appsettings.json b/src/SMMTracker.WebUI/appsettings.json deleted file mode 100644 index 6c290c8..0000000 --- a/src/SMMTracker.WebUI/appsettings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, -<<<<<<< HEAD:BlazorApp1/appsettings.json - "AllowedHosts": "*", - "TelegramBotToken": "8450218559:AAGCQdk6hnrtP8aFZpZM-bCc7tCWeKNWaIE", - "ConnectionStrings": { - "DefaultConnection": "Data Source=|DataDirectory|\\DataBase.db" - } -======= - "AllowedHosts": "*", - "TelegramBotToken": "8450218559:AAGCQdk6hnrtP8aFZpZM-bCc7tCWeKNWaIE" ->>>>>>> masha:src/SMMTracker.WebUI/appsettings.json -} diff --git a/src/SMMTracker.WebUI/wwwroot/js/telegramLogin.js b/src/SMMTracker.WebUI/wwwroot/js/telegramLogin.js deleted file mode 100644 index ae95ebd..0000000 --- a/src/SMMTracker.WebUI/wwwroot/js/telegramLogin.js +++ /dev/null @@ -1,27 +0,0 @@ -window.telegramLogin = { - renderWidget: function (elementId, dotNetMethodName) { - // Эта функция будет вызвана Telegram после успешной авторизации - window.onTelegramAuth = (user) => { - // Вызываем C# метод, имя которого нам передали из Blazor -<<<<<<< HEAD:BlazorApp1/wwwroot/js/telegramLogin.js - DotNet.invokeMethodAsync('BlazorApp1', dotNetMethodName, user); -======= - DotNet.invokeMethodAsync('SMMTracker.WebUI', dotNetMethodName, user); ->>>>>>> masha:src/SMMTracker.WebUI/wwwroot/js/telegramLogin.js - }; - - // Создаем и добавляем на страницу тег