using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using OnlyPrompt.Backend.ApiModels.UserProfile; using OnlyPrompt.Backend.Database; using OnlyPrompt.Backend.Database.Models; using OnlyPrompt.Backend.Utils; using System.ComponentModel.DataAnnotations; namespace OnlyPrompt.Backend.Controllers { [ApiController] [Route("api/v1/profiles")] [Authorize(Roles = ModelConstants.UserRole)] public class ProfileController : BaseController { private static ValidationProblem SlugExistsProblem = TypedResults.ValidationProblem(new Dictionary { { nameof(UserProfileModel.Slug), new[] { "Slug already exists." } } }); private static ValidationProblem UserNameExistsProblem = TypedResults.ValidationProblem(new Dictionary { { nameof(UserModel.UserName), new[] { "Username is already taken." } } }); public ProfileController(OnlyPromptContext db, IMapper mapper) : base(db, mapper) { } [HttpGet("self")] public async Task, Ok>> GetSelfProfileAsync() { var userId = User.GetUserId(); if (userId is null) return TypedResults.NotFound("Profile not found."); var profile = await _db.UserProfiles .Where(up => up.Id == userId.Value) .Select(up => new ApiUserProfile( up.DisplayName, up.Slug, up.Bio, up.AvatarUrl, up.Specialities, _db.Reviews.Where(r => r.Prompt.CreatorId == up.Id).Average(r => (double?)r.Rating) ?? 0.0, _db.Subscriptions.Count(s => s.SubscribedToId == up.Id) )) .FirstOrDefaultAsync(); if (profile is null) return TypedResults.NotFound("Profile not found."); return TypedResults.Ok(profile); } [HttpGet("{id}")] public async Task, Ok>> GetProfileAsync(Identifier id) { var userId = User.GetUserId(); var profile = await _db.UserProfiles.OfIdentifer(id) .ProjectTo(_mapper.ConfigurationProvider) .FirstOrDefaultAsync(); if (profile is null) return TypedResults.NotFound("Profile not found or is private."); return TypedResults.Ok(profile); } [HttpGet] public async Task GetCreatorsAsync( [Range(0, int.MaxValue)] int offset = 0, [Range(1, 100)] int limit = 20, [FromQuery] string sort = "popular", [FromQuery] string? search = null ) { var userId = User.GetUserId(); var query = _db.UserProfiles.Where(up => up.Id != userId); if (string.IsNullOrWhiteSpace(search) == false) query = query.Where(up => up.DisplayName.Contains(search) || up.Slug.Contains(search) || up.User.UserName.Contains(search) || (up.Bio != null && up.Bio.Contains(search))); var projected = query.Select(up => new ApiCreatorCard( up.Id, up.DisplayName, up.Slug, up.Bio, up.AvatarUrl, _db.Reviews.Where(r => r.Prompt.CreatorId == up.Id).Average(r => (double?)r.Rating) ?? 0.0, _db.Subscriptions.Count(s => s.SubscribedToId == up.Id), _db.Prompts.Count(p => p.CreatorId == up.Id), _db.Subscriptions.Any(s => s.SubscribedToId == up.Id && s.SubscriberId == userId) )); var allCreators = await projected.ToArrayAsync(); return (sort switch { "rating" => allCreators.OrderByDescending(c => c.AverageRating), "new" => allCreators.OrderByDescending(c => c.UserId), "prompts" => allCreators.OrderByDescending(c => c.PromptCount), _ => allCreators.OrderByDescending(c => c.Subscribers), }).Skip(offset).Take(limit).ToArray(); } [HttpPut] public async Task, Ok>> UpdateProfileAsync([FromBody] ApiUpdateProfileRequest request) { var self = await GetUserProfileAsync(); if (self is null) return TypedResults.NotFound("Profile not found."); var user = await GetUserAsync(); if (user is null) return TypedResults.NotFound("User not found."); if (string.IsNullOrEmpty(request.UserName) == false) { if (await _db.Users.AnyAsync(u => u.UserName == request.UserName && u.Id != user.Id)) return UserNameExistsProblem; user.UserName = request.UserName; } if (string.IsNullOrEmpty(request.Slug) == false) { if (await _db.UserProfiles.AnyAsync(up => up.Slug == request.Slug && up.Id != self.Id)) return SlugExistsProblem; self.Slug = request.Slug; } if(request.AvatarUrl is not null) self.AvatarUrl = request.AvatarUrl; if(request.Bio is not null) self.Bio = request.Bio; if(request.Specialities is not null) self.Specialities = request.Specialities; if (string.IsNullOrEmpty(request.DisplayName) == false) self.DisplayName = request.DisplayName; self.IsPublic = request.IsPublic; await _db.SaveChangesAsync(); var result = new ApiUserProfile( self.DisplayName, self.Slug, self.Bio, self.AvatarUrl, self.Specialities, await _db.Reviews.Where(r => r.Prompt.CreatorId == self.Id).AverageAsync(r => (double?)r.Rating) ?? 0.0, await _db.Subscriptions.CountAsync(s => s.SubscribedToId == self.Id) ); return TypedResults.Ok(result); } } }