using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.Entities; using Sozsoft.Platform.FileManagement; using Sozsoft.Platform.Identity.Dto; using Sozsoft.Platform.Extensions; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; using Microsoft.AspNetCore.Mvc; namespace Sozsoft.Platform.Intranet; [Authorize] public class IntranetAppService : PlatformAppService, IIntranetAppService { private readonly ICurrentTenant _currentTenant; private readonly BlobManager _blobContainer; private readonly IConfiguration _configuration; private readonly IIdentityUserAppService _identityUserAppService; private readonly IIdentityUserRepository _identityUserRepository; private readonly IRepository _departmentRepository; private readonly IRepository _jobPositionRepository; private readonly IRepository _announcementRepository; private readonly IRepository _surveyRepository; private readonly IRepository _surveyResponseRepository; private readonly IRepository _surveyAnswerRepository; private readonly IRepository _socialPostRepository; private readonly IRepository _socialCommentRepository; private readonly IRepository _socialLikeRepository; private readonly IRepository _socialMediaRepository; private readonly IRepository _socialPollOptionRepository; public IntranetAppService( ICurrentTenant currentTenant, BlobManager blobContainer, IConfiguration configuration, IIdentityUserAppService identityUserAppService, IIdentityUserRepository identityUserRepository, IRepository departmentRepository, IRepository jobPositionRepository, IRepository announcementRepository, IRepository surveyRepository, IRepository surveyResponseRepository, IRepository surveyAnswerRepository, IRepository socialPostRepository, IRepository socialCommentRepository, IRepository socialLikeRepository, IRepository socialMediaRepository, IRepository socialPollOptionRepository ) { _currentTenant = currentTenant; _blobContainer = blobContainer; _configuration = configuration; _identityUserAppService = identityUserAppService; _identityUserRepository = identityUserRepository; _departmentRepository = departmentRepository; _jobPositionRepository = jobPositionRepository; _announcementRepository = announcementRepository; _surveyRepository = surveyRepository; _surveyResponseRepository = surveyResponseRepository; _surveyAnswerRepository = surveyAnswerRepository; _socialPostRepository = socialPostRepository; _socialCommentRepository = socialCommentRepository; _socialLikeRepository = socialLikeRepository; _socialMediaRepository = socialMediaRepository; _socialPollOptionRepository = socialPollOptionRepository; } [UnitOfWork] public async Task GetIntranetDashboardAsync() { return new IntranetDashboardDto { Birthdays = await GetBirthdaysAsync(), //1 Documents = await GetIntranetDocumentsAsync(BlobContainerNames.Intranet), //2 Announcements = await GetAnnouncementsAsync(), //3 Surveys = await GetSurveysAsync(), //4 SocialPosts = await GetSocialPostsAsync(), //5 }; } private async Task> GetBirthdaysAsync() { var today = DateTime.Now; var users = await _identityUserRepository.GetListAsync(); var userList = users .Where(u => { var birthDate = u.GetBirthDate(); return birthDate.HasValue && birthDate.Value.Day == today.Day && birthDate.Value.Month == today.Month; }) .ToList(); var allDepartments = await _departmentRepository.GetListAsync(); var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name); var allJobPositions = await _jobPositionRepository.GetListAsync(); var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j); var result = ObjectMapper.Map, List>(userList); for (var i = 0; i < userList.Count; i++) { var user = userList[i]; result[i].BirthDate = user.GetBirthDate(); result[i].WorkHour = user.GetWorkHour(); result[i].Nationality = user.GetNationality(); var deptId = user.GetDepartmentId(); if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName)) { result[i].DepartmentId = deptId; result[i].Departments = [ new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true } ]; } var jobPosId = user.GetJobPositionId(); if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition)) { result[i].JobPositionId = jobPosId; result[i].JobPositions = [ new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true } ]; } } return result; } private async Task> GetAnnouncementsAsync() { var announcements = await _announcementRepository.GetListAsync(); var announcementDtos = new List(); var allDepartments = await _departmentRepository.GetListAsync(); var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name); var allJobPositions = await _jobPositionRepository.GetListAsync(); var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j); foreach (var announcement in announcements) { var dto = ObjectMapper.Map(announcement); var user = await _identityUserRepository.FindAsync(announcement.UserId ?? Guid.Empty); if (user != null) { var userVm = ObjectMapper.Map(user); userVm.BirthDate = user.GetBirthDate(); userVm.WorkHour = user.GetWorkHour(); userVm.Nationality = user.GetNationality(); var deptId = user.GetDepartmentId(); if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName)) { userVm.DepartmentId = deptId; userVm.Departments = [ new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true } ]; } var jobPosId = user.GetJobPositionId(); if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition)) { userVm.JobPositionId = jobPosId; userVm.JobPositions = [ new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true } ]; } dto.User = userVm; } announcementDtos.Add(dto); } return announcementDtos; } private async Task> GetSurveysAsync() { var queryable = await _surveyRepository.GetQueryableAsync(); var surveys = await AsyncExecuter.ToListAsync( queryable .AsNoTracking() .Where(s => s.Status == "active") .Include(s => s.Questions) .ThenInclude(q => q.Options) ); var dtos = ObjectMapper.Map, List>(surveys); // Tüm anketler için mevcut kullanıcının cevabını çek (anonim dahil — CreatorId ile) if (CurrentUser.IsAuthenticated) { var allSurveyIds = surveys.Select(s => s.Id).ToList(); if (allSurveyIds.Any()) { var rq = await _surveyResponseRepository.GetQueryableAsync(); var myResponses = await AsyncExecuter.ToListAsync( rq.AsNoTracking() .Where(r => allSurveyIds.Contains(r.SurveyId) && (r.UserId == CurrentUser.Id || r.CreatorId == CurrentUser.Id)) .Include(r => r.Answers) ); var responseMap = myResponses.ToDictionary(r => r.SurveyId); for (var i = 0; i < surveys.Count; i++) { if (responseMap.TryGetValue(surveys[i].Id, out var myResponse)) dtos[i].MyResponse = ObjectMapper.Map(myResponse); } } } return dtos; } private async Task> GetSocialPostsAsync() { var queryable = await _socialPostRepository .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); var socialPosts = await AsyncExecuter.ToListAsync(queryable.OrderByDescending(p => p.CreationTime)); var dtos = ObjectMapper.Map, List>(socialPosts); // Collect all unique user IDs to resolve in a single query var userIds = dtos .Select(p => p.UserId) .Union(dtos.SelectMany(p => p.Comments.Select(c => c.UserId))) .Union(dtos.SelectMany(p => p.Likes.Select(l => l.UserId))) .Where(id => id.HasValue) .Select(id => id!.Value) .Distinct() .ToList(); if (userIds.Count > 0) { var allDepartments = await _departmentRepository.GetListAsync(); var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name); var allJobPositions = await _jobPositionRepository.GetListAsync(); var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j); var users = await _identityUserRepository.GetListAsync(); var userMap = users .Where(u => userIds.Contains(u.Id)) .ToDictionary(u => u.Id, u => { var vm = ObjectMapper.Map(u); vm.BirthDate = u.GetBirthDate(); vm.WorkHour = u.GetWorkHour(); vm.Nationality = u.GetNationality(); var deptId = u.GetDepartmentId(); if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName)) { vm.DepartmentId = deptId; vm.Departments = [ new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true } ]; } var jobPosId = u.GetJobPositionId(); if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition)) { vm.JobPositionId = jobPosId; vm.JobPositions = [ new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true } ]; } return vm; }); foreach (var dto in dtos) { if (dto.UserId.HasValue && userMap.TryGetValue(dto.UserId.Value, out var postUser)) dto.User = postUser; foreach (var comment in dto.Comments) if (comment.UserId.HasValue && userMap.TryGetValue(comment.UserId.Value, out var commentUser)) comment.User = commentUser; foreach (var like in dto.Likes) if (like.UserId.HasValue && userMap.TryGetValue(like.UserId.Value, out var likeUser)) like.User = likeUser; } } foreach (var dto in dtos) { dto.IsOwnPost = dto.UserId == CurrentUser.Id; dto.IsLiked = dto.Likes.Any(l => l.UserId == CurrentUser.Id); } await EnrichPollOptionsAsync(dtos); return dtos; } private async Task EnrichPollOptionsAsync(IEnumerable dtos) { var pollMediaIds = dtos .Where(d => d.Media?.Type == "poll") .Select(d => d.Media!.Id) .ToList(); if (pollMediaIds.Count == 0) return; var optionsQueryable = await _socialPollOptionRepository.GetQueryableAsync(); var allOptions = await AsyncExecuter.ToListAsync( optionsQueryable.Where(o => pollMediaIds.Contains(o.SocialMediaId) && !o.IsDeleted)); var optionsByMedia = allOptions .GroupBy(o => o.SocialMediaId) .ToDictionary(g => g.Key, g => g.ToList()); foreach (var dto in dtos) { if (dto.Media?.Type == "poll" && optionsByMedia.TryGetValue(dto.Media.Id, out var opts)) dto.Media.PollOptions = ObjectMapper.Map, List>(opts); } } public async Task> GetIntranetDocumentsAsync(string folderPath) { var items = new List(); var cdnBasePath = _configuration["App:CdnPath"]; if (string.IsNullOrEmpty(cdnBasePath)) { Logger.LogWarning("CDN path is not configured"); return items; } var tenantId = _currentTenant.Id?.ToString() ?? "host"; var fullPath = Path.Combine(cdnBasePath, tenantId); if (!string.IsNullOrEmpty(folderPath)) { fullPath = Path.Combine(fullPath, folderPath); } if (!Directory.Exists(fullPath)) { Logger.LogWarning($"Directory not found: {fullPath}"); return items; } var files = Directory.GetFiles(fullPath); foreach (var file in files) { var fileInfo = new FileInfo(file); var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}"; items.Add(new FileItemDto { Id = Guid.NewGuid().ToString(), Name = fileInfo.Name, Type = "file", Size = fileInfo.Length, Extension = fileInfo.Extension, MimeType = GetMimeType(fileInfo.Extension), CreatedAt = fileInfo.CreationTime, ModifiedAt = fileInfo.LastWriteTime, Path = relativePath, ParentId = string.Empty, IsReadOnly = false, ChildCount = 0, TenantId = _currentTenant.Id?.ToString() }); } return items.OrderBy(x => x.Name).ToList(); } private string GetMimeType(string extension) { return extension.ToLowerInvariant() switch { ".pdf" => "application/pdf", ".doc" => "application/msword", ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".xls" => "application/vnd.ms-excel", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".ppt" => "application/vnd.ms-powerpoint", ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", ".gif" => "image/gif", ".txt" => "text/plain", ".zip" => "application/zip", ".rar" => "application/x-rar-compressed", _ => "application/octet-stream" }; } public async Task CreateSurveyResponseAsync(SubmitSurveyInput input) { var survey = await _surveyRepository.GetAsync(input.SurveyId); SurveyResponse? response = null; if (CurrentUser.IsAuthenticated) { var responseQueryable = await _surveyResponseRepository.GetQueryableAsync(); response = await AsyncExecuter.FirstOrDefaultAsync( responseQueryable.Where(r => r.SurveyId == input.SurveyId && (r.UserId == CurrentUser.Id || r.CreatorId == CurrentUser.Id)) ); } if (response != null) { var answerQueryable = await _surveyAnswerRepository.GetQueryableAsync(); var existingAnswers = await AsyncExecuter.ToListAsync( answerQueryable.Where(a => a.ResponseId == response.Id) ); var existingAnswerMap = existingAnswers.ToDictionary(x => x.QuestionId); foreach (var inputAnswer in input.Answers) { if (existingAnswerMap.TryGetValue(inputAnswer.QuestionId, out var existingAnswer)) { existingAnswer.Value = inputAnswer.Value ?? string.Empty; existingAnswer.QuestionType = inputAnswer.QuestionType; await _surveyAnswerRepository.UpdateAsync(existingAnswer); } else { await _surveyAnswerRepository.InsertAsync(new SurveyAnswer(Guid.NewGuid()) { ResponseId = response.Id, QuestionId = inputAnswer.QuestionId, QuestionType = inputAnswer.QuestionType, Value = inputAnswer.Value ?? string.Empty }); } } response.SubmissionTime = Clock.Now; await _surveyResponseRepository.UpdateAsync(response); return; } var newResponse = new SurveyResponse(Guid.NewGuid()) { SurveyId = input.SurveyId, UserId = survey.IsAnonymous ? null : CurrentUser.Id, SubmissionTime = Clock.Now, Answers = input.Answers.Select(a => new SurveyAnswer(Guid.NewGuid()) { QuestionId = a.QuestionId, QuestionType = a.QuestionType, Value = a.Value ?? string.Empty }).ToList() }; await _surveyResponseRepository.InsertAsync(newResponse); survey.Responses++; await _surveyRepository.UpdateAsync(survey); } public async Task CreateSocialPostAsync(CreateSocialPostInput input) { var post = new SocialPost(Guid.NewGuid()) { UserId = CurrentUser.Id, Content = input.Content, }; if (!string.IsNullOrWhiteSpace(input.LocationJson)) { var locData = System.Text.Json.JsonSerializer.Deserialize(input.LocationJson); post.Location = new SocialLocation(Guid.NewGuid()) { SocialPostId = post.Id, Name = locData.TryGetProperty("name", out var nameProp) ? nameProp.GetString() ?? string.Empty : string.Empty, Address = locData.TryGetProperty("address", out var addrProp) ? addrProp.GetString() : null, Lat = locData.TryGetProperty("lat", out var latProp) && latProp.TryGetDouble(out var latVal) ? latVal : null, Lng = locData.TryGetProperty("lng", out var lngProp) && lngProp.TryGetDouble(out var lngVal) ? lngVal : null, PlaceId = locData.TryGetProperty("placeId", out var placeIdProp) ? placeIdProp.GetString() : null, }; } if (input.Media != null) { var media = new SocialMedia(Guid.NewGuid()) { SocialPostId = post.Id, Type = input.Media.Type, Urls = input.Media.Urls ?? [], PollQuestion = input.Media.PollQuestion, }; if (input.Media.PollOptions is { Count: > 0 }) { media.PollOptions = input.Media.PollOptions .Select(o => new SocialPollOption(Guid.NewGuid()) { SocialMediaId = media.Id, Text = o.Text, Votes = 0, }) .ToList(); } post.Media = media; } await _socialPostRepository.InsertAsync(post, autoSave: true); // Reload with full navigation properties for mapping var queryable = await _socialPostRepository .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); var savedPost = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(p => p.Id == post.Id)); var dto = ObjectMapper.Map(savedPost!); dto.IsOwnPost = true; if (CurrentUser.Id.HasValue) { var user = await _identityUserRepository.FindAsync(CurrentUser.Id.Value); if (user != null) dto.User = ObjectMapper.Map(user); } await EnrichPollOptionsAsync([dto]); return dto; } public async Task DeleteSocialPostAsync(Guid id) { var post = await _socialPostRepository.GetAsync(id); if (post.UserId != CurrentUser.Id) throw new Volo.Abp.Authorization.AbpAuthorizationException("You can only delete your own posts."); await _socialPostRepository.DeleteAsync(id); } [HttpPost("api/app/intranet/like-social-post")] public async Task LikeSocialPostAsync(Guid id) { var post = await _socialPostRepository.GetAsync(id); var likeQueryable = await _socialLikeRepository.GetQueryableAsync(); var existingLike = await AsyncExecuter.FirstOrDefaultAsync( likeQueryable.Where(l => l.SocialPostId == id && l.UserId == CurrentUser.Id)); bool isNowLiked; if (existingLike != null) { await _socialLikeRepository.DeleteAsync(existingLike.Id); post.LikeCount = Math.Max(0, post.LikeCount - 1); isNowLiked = false; } else { await _socialLikeRepository.InsertAsync(new SocialLike(Guid.NewGuid()) { SocialPostId = id, UserId = CurrentUser.Id, }); post.LikeCount++; isNowLiked = true; } post.IsLiked = isNowLiked; await _socialPostRepository.UpdateAsync(post, autoSave: true); var queryable = await _socialPostRepository .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); var updated = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(p => p.Id == id)); var dto = ObjectMapper.Map(updated!); // Resolve user info var userIds = new List { dto.UserId } .Union(dto.Comments.Select(c => c.UserId)) .Union(dto.Likes.Select(l => l.UserId)) .Where(uid => uid.HasValue) .Select(uid => uid!.Value) .Distinct() .ToList(); if (userIds.Count > 0) { var allDepartments = await _departmentRepository.GetListAsync(); var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name); var allJobPositions = await _jobPositionRepository.GetListAsync(); var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j); var users = await _identityUserRepository.GetListAsync(); var userMap = users.Where(u => userIds.Contains(u.Id)).ToDictionary(u => u.Id, u => { var vm = ObjectMapper.Map(u); var deptId = u.GetDepartmentId(); if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName)) { vm.DepartmentId = deptId; vm.Departments = [new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true }]; } var jobPosId = u.GetJobPositionId(); if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition)) { vm.JobPositionId = jobPosId; vm.JobPositions = [new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true }]; } return vm; }); if (dto.UserId.HasValue && userMap.TryGetValue(dto.UserId.Value, out var postUser)) dto.User = postUser; foreach (var comment in dto.Comments) if (comment.UserId.HasValue && userMap.TryGetValue(comment.UserId.Value, out var commentUser)) comment.User = commentUser; foreach (var like in dto.Likes) if (like.UserId.HasValue && userMap.TryGetValue(like.UserId.Value, out var likeUser)) like.User = likeUser; } await EnrichPollOptionsAsync([dto]); dto.IsLiked = isNowLiked; dto.IsOwnPost = dto.UserId == CurrentUser.Id; return dto; } [HttpPost("api/app/intranet/comment-social-post")] public async Task CommentSocialPostAsync(Guid id, string content) { var comment = new SocialComment(Guid.NewGuid()) { SocialPostId = id, UserId = CurrentUser.Id, Content = content, }; await _socialCommentRepository.InsertAsync(comment, autoSave: true); var dto = ObjectMapper.Map(comment); if (CurrentUser.Id.HasValue) { var user = await _identityUserRepository.FindAsync(CurrentUser.Id.Value); if (user != null) dto.User = ObjectMapper.Map(user); } return dto; } [HttpPost("api/app/intranet/vote-social-poll")] public async Task VoteSocialPollAsync(Guid postId, Guid optionId) { var post = await _socialPostRepository.GetAsync(postId); var mediaQueryable = await _socialMediaRepository.GetQueryableAsync(); var media = await AsyncExecuter.FirstOrDefaultAsync( mediaQueryable.Where(m => m.SocialPostId == postId)); if (media == null) return; var option = await _socialPollOptionRepository.GetAsync(optionId); option.Votes++; await _socialPollOptionRepository.UpdateAsync(option, autoSave: true); media.PollTotalVotes = (media.PollTotalVotes ?? 0) + 1; media.PollUserVoteId = optionId.ToString(); await _socialMediaRepository.UpdateAsync(media, autoSave: true); } [HttpPost("api/app/intranet/{id}/announcement-view")] public async Task IncrementAnnouncementViewCountAsync(Guid id) { var announcement = await _announcementRepository.GetAsync(id); announcement.ViewCount++; await _announcementRepository.UpdateAsync(announcement, autoSave: true); } }