2026-05-05 17:59:30 +00:00
|
|
|
|
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;
|
2026-05-06 19:07:30 +00:00
|
|
|
|
using Sozsoft.Platform.Identity;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
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;
|
2026-05-06 07:54:04 +00:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
namespace Sozsoft.Platform.Intranet;
|
|
|
|
|
|
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
|
public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ICurrentTenant _currentTenant;
|
|
|
|
|
|
private readonly BlobManager _blobContainer;
|
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
private readonly IRepository<Event, Guid> _eventRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
private readonly IIdentityUserAppService _identityUserAppService;
|
|
|
|
|
|
private readonly IIdentityUserRepository _identityUserRepository;
|
|
|
|
|
|
private readonly IRepository<Department, Guid> _departmentRepository;
|
|
|
|
|
|
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
|
|
|
|
|
|
private readonly IRepository<Announcement, Guid> _announcementRepository;
|
|
|
|
|
|
private readonly IRepository<Survey, Guid> _surveyRepository;
|
2026-05-06 07:54:04 +00:00
|
|
|
|
private readonly IRepository<SurveyResponse, Guid> _surveyResponseRepository;
|
|
|
|
|
|
private readonly IRepository<SurveyAnswer, Guid> _surveyAnswerRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
private readonly IRepository<SocialPost, Guid> _socialPostRepository;
|
2026-05-06 13:47:18 +00:00
|
|
|
|
private readonly IRepository<SocialComment, Guid> _socialCommentRepository;
|
|
|
|
|
|
private readonly IRepository<SocialLike, Guid> _socialLikeRepository;
|
|
|
|
|
|
private readonly IRepository<SocialMedia, Guid> _socialMediaRepository;
|
|
|
|
|
|
private readonly IRepository<SocialPollOption, Guid> _socialPollOptionRepository;
|
2026-05-07 14:25:06 +00:00
|
|
|
|
private readonly IRepository<EventComment, Guid> _eventCommentRepository;
|
2026-05-08 18:11:56 +00:00
|
|
|
|
private readonly IRepository<EventLike, Guid> _eventLikeRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
public IntranetAppService(
|
|
|
|
|
|
ICurrentTenant currentTenant,
|
|
|
|
|
|
BlobManager blobContainer,
|
|
|
|
|
|
IConfiguration configuration,
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
IRepository<Event, Guid> eventRepository,
|
2026-05-05 17:59:30 +00:00
|
|
|
|
IIdentityUserAppService identityUserAppService,
|
|
|
|
|
|
IIdentityUserRepository identityUserRepository,
|
|
|
|
|
|
IRepository<Department, Guid> departmentRepository,
|
|
|
|
|
|
IRepository<JobPosition, Guid> jobPositionRepository,
|
|
|
|
|
|
IRepository<Announcement, Guid> announcementRepository,
|
|
|
|
|
|
IRepository<Survey, Guid> surveyRepository,
|
2026-05-06 07:54:04 +00:00
|
|
|
|
IRepository<SurveyResponse, Guid> surveyResponseRepository,
|
|
|
|
|
|
IRepository<SurveyAnswer, Guid> surveyAnswerRepository,
|
2026-05-06 13:47:18 +00:00
|
|
|
|
IRepository<SocialPost, Guid> socialPostRepository,
|
|
|
|
|
|
IRepository<SocialComment, Guid> socialCommentRepository,
|
|
|
|
|
|
IRepository<SocialLike, Guid> socialLikeRepository,
|
|
|
|
|
|
IRepository<SocialMedia, Guid> socialMediaRepository,
|
2026-05-07 14:25:06 +00:00
|
|
|
|
IRepository<SocialPollOption, Guid> socialPollOptionRepository,
|
2026-05-08 18:11:56 +00:00
|
|
|
|
IRepository<EventComment, Guid> eventCommentRepository,
|
|
|
|
|
|
IRepository<EventLike, Guid> eventLikeRepository
|
2026-05-05 17:59:30 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentTenant = currentTenant;
|
|
|
|
|
|
_blobContainer = blobContainer;
|
|
|
|
|
|
_configuration = configuration;
|
2026-05-06 19:07:30 +00:00
|
|
|
|
_eventRepository = eventRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
_identityUserAppService = identityUserAppService;
|
|
|
|
|
|
_identityUserRepository = identityUserRepository;
|
|
|
|
|
|
_departmentRepository = departmentRepository;
|
|
|
|
|
|
_jobPositionRepository = jobPositionRepository;
|
|
|
|
|
|
_announcementRepository = announcementRepository;
|
|
|
|
|
|
_surveyRepository = surveyRepository;
|
2026-05-06 07:54:04 +00:00
|
|
|
|
_surveyResponseRepository = surveyResponseRepository;
|
|
|
|
|
|
_surveyAnswerRepository = surveyAnswerRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
_socialPostRepository = socialPostRepository;
|
2026-05-06 13:47:18 +00:00
|
|
|
|
_socialCommentRepository = socialCommentRepository;
|
|
|
|
|
|
_socialLikeRepository = socialLikeRepository;
|
|
|
|
|
|
_socialMediaRepository = socialMediaRepository;
|
|
|
|
|
|
_socialPollOptionRepository = socialPollOptionRepository;
|
2026-05-07 14:25:06 +00:00
|
|
|
|
_eventCommentRepository = eventCommentRepository;
|
2026-05-08 18:11:56 +00:00
|
|
|
|
_eventLikeRepository = eventLikeRepository;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
public async Task<IntranetDashboardDto> 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
|
2026-05-06 19:07:30 +00:00
|
|
|
|
Events = await GetUpcomingEventsAsync(), //6
|
2026-05-05 17:59:30 +00:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
private async Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)> GetUserLookupDictionariesAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var departments = await _departmentRepository.GetListAsync();
|
|
|
|
|
|
var jobPositions = await _jobPositionRepository.GetListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
departments.ToDictionary(d => d.Id, d => d.Name),
|
|
|
|
|
|
jobPositions.ToDictionary(j => j.Id, j => j)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private UserInfoViewModel MapUserInfoViewModel(
|
|
|
|
|
|
IdentityUser user,
|
|
|
|
|
|
IReadOnlyDictionary<Guid, string> departmentDict,
|
|
|
|
|
|
IReadOnlyDictionary<Guid, JobPosition> jobPositionDict)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ObjectMapper
|
|
|
|
|
|
.Map<IdentityUser, UserInfoViewModel>(user)
|
|
|
|
|
|
.MapDepartmentAndJobPositionAssignments(departmentDict, jobPositionDict);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<List<EventDto>> GetUpcomingEventsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var queryable = await _eventRepository
|
|
|
|
|
|
.WithDetailsAsync(e => e.Category, e => e.Type);
|
|
|
|
|
|
|
|
|
|
|
|
var events = await AsyncExecuter.ToListAsync(
|
|
|
|
|
|
queryable.Where(e => e.isPublished).OrderByDescending(e => e.CreationTime)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (events.Count == 0)
|
|
|
|
|
|
return [];
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
var eventIds = events.Select(e => e.Id).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// Load all comments for these events
|
|
|
|
|
|
var commentsQueryable = await _eventCommentRepository.GetQueryableAsync();
|
|
|
|
|
|
var allComments = await AsyncExecuter.ToListAsync(
|
|
|
|
|
|
commentsQueryable.Where(c => eventIds.Contains(c.EventId)).OrderBy(c => c.CreationTime)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-05-08 18:11:56 +00:00
|
|
|
|
// Load all likes for these events
|
|
|
|
|
|
var likesQueryable = await _eventLikeRepository.GetQueryableAsync();
|
|
|
|
|
|
var allLikes = await AsyncExecuter.ToListAsync(
|
|
|
|
|
|
likesQueryable.Where(l => eventIds.Contains(l.EventId))
|
|
|
|
|
|
);
|
|
|
|
|
|
var likedEventIds = allLikes
|
|
|
|
|
|
.Where(l => l.UserId == CurrentUser.Id)
|
|
|
|
|
|
.Select(l => l.EventId)
|
|
|
|
|
|
.ToHashSet();
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
// Collect all unique user IDs
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var userIds = new HashSet<Guid>();
|
|
|
|
|
|
foreach (var evt in events)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (evt.UserId.HasValue)
|
|
|
|
|
|
userIds.Add(evt.UserId.Value);
|
2026-05-07 14:25:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
foreach (var comment in allComments)
|
|
|
|
|
|
{
|
|
|
|
|
|
userIds.Add(comment.UserId);
|
2026-05-06 19:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var users = await _identityUserRepository.GetListAsync();
|
|
|
|
|
|
var userDict = users
|
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
|
|
|
|
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
var commentsByEvent = allComments.GroupBy(c => c.EventId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var result = new List<EventDto>();
|
|
|
|
|
|
foreach (var evt in events)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!evt.UserId.HasValue || !userDict.TryGetValue(evt.UserId.Value, out var user))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
var commentDtos = new List<EventCommentDto>();
|
|
|
|
|
|
if (commentsByEvent.TryGetValue(evt.Id, out var eventComments))
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var c in eventComments)
|
|
|
|
|
|
{
|
|
|
|
|
|
commentDtos.Add(new EventCommentDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = c.Id,
|
|
|
|
|
|
EventId = c.EventId,
|
|
|
|
|
|
UserId = c.UserId,
|
|
|
|
|
|
User = userDict.TryGetValue(c.UserId, out var commentUser) ? commentUser : null,
|
|
|
|
|
|
Content = c.Content,
|
|
|
|
|
|
Likes = c.Likes,
|
|
|
|
|
|
CreationTime = c.CreationTime
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var calendarEvent = new EventDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = evt.Id,
|
|
|
|
|
|
Name = evt.Name,
|
|
|
|
|
|
Description = evt.Description,
|
|
|
|
|
|
TypeName = evt.Type?.Name,
|
|
|
|
|
|
CategoryName = evt.Category?.Name,
|
|
|
|
|
|
Date = evt.Date,
|
|
|
|
|
|
Place = evt.Place,
|
|
|
|
|
|
User = user,
|
|
|
|
|
|
ParticipantsCount = evt.ParticipantsCount,
|
|
|
|
|
|
Likes = evt.Likes,
|
2026-05-08 18:11:56 +00:00
|
|
|
|
IsLiked = likedEventIds.Contains(evt.Id),
|
2026-05-07 14:25:06 +00:00
|
|
|
|
IsPublished = evt.isPublished,
|
|
|
|
|
|
Photos = evt.Photos,
|
|
|
|
|
|
Comments = commentDtos
|
2026-05-06 19:07:30 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
result.Add(calendarEvent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
public async Task<List<EventCommentDto>> GetEventCommentsAsync(Guid eventId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var commentsQueryable = await _eventCommentRepository.GetQueryableAsync();
|
|
|
|
|
|
var comments = await AsyncExecuter.ToListAsync(
|
|
|
|
|
|
commentsQueryable.Where(c => c.EventId == eventId).OrderBy(c => c.CreationTime)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (comments.Count == 0)
|
|
|
|
|
|
return [];
|
|
|
|
|
|
|
|
|
|
|
|
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
|
|
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
|
|
|
|
|
var users = await _identityUserRepository.GetListAsync();
|
|
|
|
|
|
var userDict = users
|
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
|
|
|
|
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
|
|
|
|
|
|
|
|
|
|
|
return comments.Select(c => new EventCommentDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = c.Id,
|
|
|
|
|
|
EventId = c.EventId,
|
|
|
|
|
|
UserId = c.UserId,
|
|
|
|
|
|
User = userDict.TryGetValue(c.UserId, out var u) ? u : null,
|
|
|
|
|
|
Content = c.Content,
|
|
|
|
|
|
Likes = c.Likes,
|
|
|
|
|
|
CreationTime = c.CreationTime
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<EventCommentDto> CreateEventCommentAsync(Guid eventId, string content)
|
|
|
|
|
|
{
|
|
|
|
|
|
var comment = new EventComment
|
|
|
|
|
|
{
|
|
|
|
|
|
EventId = eventId,
|
|
|
|
|
|
UserId = CurrentUser.Id ?? Guid.Empty,
|
|
|
|
|
|
Content = content,
|
|
|
|
|
|
Likes = 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
comment = await _eventCommentRepository.InsertAsync(comment, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
|
|
|
|
|
var user = await _identityUserRepository.FindAsync(comment.UserId);
|
|
|
|
|
|
var userViewModel = user != null ? MapUserInfoViewModel(user, departmentDict, jobPositionDict) : null;
|
|
|
|
|
|
|
|
|
|
|
|
return new EventCommentDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = comment.Id,
|
|
|
|
|
|
EventId = comment.EventId,
|
|
|
|
|
|
UserId = comment.UserId,
|
|
|
|
|
|
User = userViewModel,
|
|
|
|
|
|
Content = comment.Content,
|
|
|
|
|
|
Likes = comment.Likes,
|
|
|
|
|
|
CreationTime = comment.CreationTime
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 18:11:56 +00:00
|
|
|
|
[HttpPost("api/app/intranet/like-event")]
|
|
|
|
|
|
public async Task<EventDto> LikeEventAsync(Guid id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var evt = await _eventRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
|
|
var likeQueryable = await _eventLikeRepository.GetQueryableAsync();
|
|
|
|
|
|
var existingLike = await AsyncExecuter.FirstOrDefaultAsync(
|
|
|
|
|
|
likeQueryable.Where(l => l.EventId == id && l.UserId == CurrentUser.Id));
|
|
|
|
|
|
|
|
|
|
|
|
bool isNowLiked;
|
|
|
|
|
|
if (existingLike != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventLikeRepository.DeleteAsync(existingLike.Id);
|
|
|
|
|
|
evt.Likes = Math.Max(0, evt.Likes - 1);
|
|
|
|
|
|
isNowLiked = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventLikeRepository.InsertAsync(new EventLike(Guid.NewGuid())
|
|
|
|
|
|
{
|
|
|
|
|
|
EventId = id,
|
|
|
|
|
|
UserId = CurrentUser.Id,
|
|
|
|
|
|
});
|
|
|
|
|
|
evt.Likes++;
|
|
|
|
|
|
isNowLiked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await _eventRepository.UpdateAsync(evt, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
return new EventDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = evt.Id,
|
|
|
|
|
|
Likes = evt.Likes,
|
|
|
|
|
|
IsLiked = isNowLiked,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 17:59:30 +00:00
|
|
|
|
private async Task<List<UserInfoViewModel>> 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();
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
return userList
|
|
|
|
|
|
.Select(user => MapUserInfoViewModel(user, departmentDict, jobPositionDict))
|
|
|
|
|
|
.ToList();
|
2026-05-05 17:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var announcements = await _announcementRepository.GetListAsync();
|
|
|
|
|
|
var announcementDtos = new List<AnnouncementDto>();
|
|
|
|
|
|
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
foreach (var announcement in announcements)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dto = ObjectMapper.Map<Announcement, AnnouncementDto>(announcement);
|
|
|
|
|
|
|
|
|
|
|
|
var user = await _identityUserRepository.FindAsync(announcement.UserId ?? Guid.Empty);
|
|
|
|
|
|
if (user != null)
|
|
|
|
|
|
{
|
2026-05-06 19:07:30 +00:00
|
|
|
|
dto.User = MapUserInfoViewModel(user, departmentDict, jobPositionDict);
|
2026-05-05 17:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
announcementDtos.Add(dto);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return announcementDtos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<List<SurveyDto>> GetSurveysAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var queryable = await _surveyRepository.GetQueryableAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var surveys = await AsyncExecuter.ToListAsync(
|
|
|
|
|
|
queryable
|
2026-05-06 07:54:04 +00:00
|
|
|
|
.AsNoTracking()
|
2026-05-05 17:59:30 +00:00
|
|
|
|
.Where(s => s.Status == "active")
|
|
|
|
|
|
.Include(s => s.Questions)
|
|
|
|
|
|
.ThenInclude(q => q.Options)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-05-06 07:54:04 +00:00
|
|
|
|
var dtos = ObjectMapper.Map<List<Survey>, List<SurveyDto>>(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<SurveyResponse, SurveyResponseDto>(myResponse);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dtos;
|
2026-05-05 17:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<List<SocialPostDto>> GetSocialPostsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var queryable = await _socialPostRepository
|
|
|
|
|
|
.WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes);
|
|
|
|
|
|
|
2026-05-06 13:47:18 +00:00
|
|
|
|
var socialPosts = await AsyncExecuter.ToListAsync(queryable.OrderByDescending(p => p.CreationTime));
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
var dtos = ObjectMapper.Map<List<SocialPost>, List<SocialPostDto>>(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)
|
|
|
|
|
|
{
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
var users = await _identityUserRepository.GetListAsync();
|
|
|
|
|
|
var userMap = users
|
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
2026-05-06 19:07:30 +00:00
|
|
|
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
2026-05-05 17:59:30 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 13:47:18 +00:00
|
|
|
|
foreach (var dto in dtos)
|
|
|
|
|
|
{
|
|
|
|
|
|
dto.IsOwnPost = dto.UserId == CurrentUser.Id;
|
|
|
|
|
|
dto.IsLiked = dto.Likes.Any(l => l.UserId == CurrentUser.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await EnrichPollOptionsAsync(dtos);
|
|
|
|
|
|
|
2026-05-05 17:59:30 +00:00
|
|
|
|
return dtos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 13:47:18 +00:00
|
|
|
|
private async Task EnrichPollOptionsAsync(IEnumerable<SocialPostDto> 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<SocialPollOption>, List<SocialPollOptionDto>>(opts);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 17:59:30 +00:00
|
|
|
|
public async Task<List<FileItemDto>> GetIntranetDocumentsAsync(string folderPath)
|
|
|
|
|
|
{
|
|
|
|
|
|
var items = new List<FileItemDto>();
|
|
|
|
|
|
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"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-05-06 07:54:04 +00:00
|
|
|
|
|
2026-05-06 13:47:18 +00:00
|
|
|
|
public async Task CreateSurveyResponseAsync(SubmitSurveyInput input)
|
2026-05-06 07:54:04 +00:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-05-06 13:47:18 +00:00
|
|
|
|
|
|
|
|
|
|
public async Task<SocialPostDto> 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<System.Text.Json.JsonElement>(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<SocialPost, SocialPostDto>(savedPost!);
|
|
|
|
|
|
dto.IsOwnPost = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentUser.Id.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var user = await _identityUserRepository.FindAsync(CurrentUser.Id.Value);
|
|
|
|
|
|
if (user != null)
|
2026-05-06 19:07:30 +00:00
|
|
|
|
{
|
|
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
|
|
|
|
|
dto.User = MapUserInfoViewModel(user, departmentDict, jobPositionDict);
|
|
|
|
|
|
}
|
2026-05-06 13:47:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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<SocialPostDto> 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<SocialPost, SocialPostDto>(updated!);
|
|
|
|
|
|
|
|
|
|
|
|
// Resolve user info
|
|
|
|
|
|
var userIds = new List<Guid?> { 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)
|
|
|
|
|
|
{
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
2026-05-06 13:47:18 +00:00
|
|
|
|
var users = await _identityUserRepository.GetListAsync();
|
2026-05-06 19:07:30 +00:00
|
|
|
|
var userMap = users
|
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
|
|
|
|
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
2026-05-06 13:47:18 +00:00
|
|
|
|
|
|
|
|
|
|
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<SocialCommentDto> 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<SocialComment, SocialCommentDto>(comment);
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentUser.Id.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var user = await _identityUserRepository.FindAsync(CurrentUser.Id.Value);
|
|
|
|
|
|
if (user != null)
|
2026-05-06 19:07:30 +00:00
|
|
|
|
{
|
|
|
|
|
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
|
|
|
|
|
dto.User = MapUserInfoViewModel(user, departmentDict, jobPositionDict);
|
|
|
|
|
|
}
|
2026-05-06 13:47:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-05-05 17:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|