using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using OnlyPrompt.Backend.ApiModels.Prompt; 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/prompts")] [Authorize(Roles = ModelConstants.UserRole)] public class PromptController : BaseController { public PromptController(OnlyPromptContext db, IMapper mapper) : base(db, mapper) { } private IQueryable GetAccessiblePrompts(Guid userId) { return _db.Prompts.Where( p => p.SubscriptionTier == null || p.Creator.Subscribers.Any( sub => sub.SubscriberId == userId && p.SubscriptionTier!.Level <= sub.SubscriptionTier!.Level ) ); } [HttpGet("{id}")] public async Task, NotFound>> GetPromptAsync(Identifier id) { var userId = User.GetUserId(); var prompt = await GetAccessiblePrompts(userId.Value) .OfIdentifer(id) .FirstOrDefaultAsync(); if (prompt is null) return TypedResults.NotFound("Prompt not found or no permission"); var apiPrompt = _mapper.Map(prompt); return TypedResults.Ok(apiPrompt); } [HttpDelete("{id}")] public async Task>> DeletePromptAsync(Identifier id) { var userId = User.GetUserId(); var isAdmin = User.IsInRole(ModelConstants.AdminRole); var count = await _db.Prompts .OfIdentifer(id) .Where(p => p.CreatorId == userId || isAdmin) .ExecuteDeleteAsync(); if (count == 0) return TypedResults.NotFound("Prompt not found or no permission"); return TypedResults.NoContent(); } [HttpPost] public async Task, NotFound>> CreatePromptAsync([FromBody] ApiCreatePromptRequest request) { var userId = User.GetUserId(); var category = await _db.Categories.FindByIdentifierAsync(request.Category); if (category is null) return TypedResults.NotFound("Category not found"); SubscriptionTierModel? subscriptionTier = null; if (request.SubscriptionTier.HasValue) { subscriptionTier = await _db.SubscriptionTiers.FirstOrDefaultAsync( t => t.Level == request.SubscriptionTier.Value && t.UserId == userId ); if (subscriptionTier is null) return TypedResults.NotFound("Subscription tier not found"); } var slug = request.Slug; if (string.IsNullOrEmpty(slug)) slug = await SlugHelper.GenerateUniqueSlugAsync(request.Title, slug => _db.Prompts.AnyAsync(p => p.Slug == slug), ModelConstants.MaxSlugLength); var prompt = new PromptModel { Id = Guid.NewGuid(), Title = request.Title, Description = request.Description, Prompt = request.Content, CreatorId = userId.Value, SubscriptionTier = subscriptionTier, Category = category, Slug = slug }; _db.Prompts.Add(prompt); await _db.SaveChangesAsync(); var apiPrompt = _mapper.Map(prompt); return TypedResults.Ok(apiPrompt); } [HttpGet("{id}/reviews")] public async Task> GetReviewsAsync(Identifier id, [FromQuery] int offset = 0, [Range(1, 200)][FromQuery] int limit = 20) { var userId = User.GetUserId(); var accessiblePrompts = GetAccessiblePrompts(userId!.Value); var reviews = await accessiblePrompts.Select(x => x.Reviews) .Skip(offset) .Take(limit) .ProjectTo(_mapper.ConfigurationProvider) .ToArrayAsync(); return TypedResults.Ok(reviews); } [HttpPut("{id}/reviews")] public async Task, NotFound>> AddReviewAsync(Identifier id, [FromBody] ApiCreateReviewRequest request) { var userId = User.GetUserId(); var prompt = await GetAccessiblePrompts(userId!.Value) .OfIdentifer(id) .FirstOrDefaultAsync(); if (prompt is null) return TypedResults.NotFound("Prompt not found or no permission"); var review = await _db.Reviews.FirstOrDefaultAsync( r => r.PromptId == prompt.Id && r.ReviewerId == userId ); if (review is null) { review = new ReviewModel { PromptId = prompt.Id, ReviewerId = userId.Value, Comment = request.Comment, Rating = request.Rating }; _db.Reviews.Add(review); } else { review.Comment = request.Comment; review.Rating = request.Rating; } await _db.SaveChangesAsync(); var apiReview = _mapper.Map(review); return TypedResults.Ok(apiReview); } [HttpDelete("{promptId}/reviews/{reviewerId}")] public async Task>> DeleteReviewAsync(Identifier promptId, Guid reviewerId) { var userId = User.GetUserId(); var isAdmin = User.IsInRole(ModelConstants.AdminRole); var count = await _db.Reviews .Where( r => (promptId.Id.HasValue ? r.PromptId == promptId.Id : r.Prompt.Slug == promptId.Slug) && (r.ReviewerId == reviewerId || isAdmin) ) .ExecuteDeleteAsync(); if (count == 0) return TypedResults.NotFound("Review not found or no permission"); return TypedResults.NoContent(); } } }