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.Subscription; using OnlyPrompt.Backend.Database; using OnlyPrompt.Backend.Database.Models; using OnlyPrompt.Backend.Utils; using System.ComponentModel.DataAnnotations; using System.Formats.Asn1; namespace OnlyPrompt.Backend.Controllers { [ApiController] [Route("api/v1/subscriptions")] [Authorize(Roles = ModelConstants.UserRole)] public class SubscriptionController : BaseController { private static ValidationProblem TierLevelExistsProblem = TypedResults.ValidationProblem(new Dictionary { { nameof(SubscriptionTierModel.Level), new[] { "Tier with this level already exists." } } }); public SubscriptionController(OnlyPromptContext db, IMapper mapper) : base(db, mapper) { } [HttpPut("{userId}/{level}")] public async Task, NotFound>> SubscribeAsync(Identifier subscribeToId, int? level = null) { var userId = User.GetUserId(); var subscribeTo = await _db.Users.Include(x => x.SubscriptionTiers.Where(st => st.Level == level)) .FirstOrDefaultAsync( user => subscribeToId.Id.HasValue ? user.Id == subscribeToId.Id.Value : user.Profile.Slug == subscribeToId.Slug ); if (subscribeTo is null) return TypedResults.NotFound($"No user found with identifier {subscribeToId}"); if (subscribeTo.Id == userId) return TypedResults.BadRequest("Cannot subscribe to yourself"); SubscriptionTierModel? tier = subscribeTo.SubscriptionTiers.FirstOrDefault(); if (level.HasValue && tier is null) return TypedResults.NotFound($"No subscription tier found for user {subscribeToId} with level {level.Value}"); var existingSubscription = await _db.Subscriptions.FirstOrDefaultAsync( sub => subscribeToId.Id.HasValue ? sub.SubscribedToId == subscribeToId.Id.Value : sub.SubscribedTo.Profile.Slug == subscribeToId.Slug && sub.SubscriberId == userId ); if (existingSubscription is null) { existingSubscription = new SubscriptionModel { SubscribedTo = subscribeTo, SubscriberId = userId.Value, SubscriptionTier = tier }; _db.Subscriptions.Add(existingSubscription); } else { existingSubscription.SubscriptionTier = tier; } await _db.SaveChangesAsync(); return TypedResults.Ok(); } [HttpGet] public async Task GetSubscriptionsAsync([Range(0, double.MaxValue)]int offset = 0, [Range(1, 100)]int limit = 20) { var userId = User.GetUserId(); var subscriptions = await _db.Subscriptions .Where(x => x.SubscriberId == userId) .OrderBy(x => x.SubscribedToId) .Skip(offset) .Take(limit) .ProjectTo(_mapper.ConfigurationProvider) .ToArrayAsync(); return subscriptions; } [HttpGet("{userId}")] public async Task GetCurrentSubscriptionAsync(Identifier subscribeToId) { var userId = User.GetUserId(); var subscription = await _db.Subscriptions .Where( sub => subscribeToId.Id.HasValue ? sub.SubscribedToId == subscribeToId.Id.Value : sub.SubscribedTo.Profile.Slug == subscribeToId.Slug && sub.SubscriberId == userId ) .ProjectTo(_mapper.ConfigurationProvider) .FirstOrDefaultAsync(); return subscription; } [HttpDelete("{userId}")] public async Task>> UnsubscribeAsync(Identifier subscribeToId) { var userId = User.GetUserId(); var count = await _db.Subscriptions .Where( sub => subscribeToId.Id.HasValue ? sub.SubscribedToId == subscribeToId.Id.Value : sub.SubscribedTo.Profile.Slug == subscribeToId.Slug && sub.SubscriberId == userId ) .ExecuteDeleteAsync(); if (count == 0) return TypedResults.NotFound($"No subscription found for user {subscribeToId}"); return TypedResults.Ok(); } [HttpPost("tiers")] public async Task, ValidationProblem>> CreateOrUpdateSubscriptionTierAsync([FromBody] ApiCreateSubscriptionTierRequest tier) { var userId = User.GetUserId(); var levelExists = await _db.SubscriptionTiers.AnyAsync(t => t.UserId == userId && t.Level == tier.Level); if (levelExists) return TierLevelExistsProblem; var model = _mapper.Map(tier); model.UserId = userId!.Value; _db.SubscriptionTiers.Add(model); await _db.SaveChangesAsync(); return TypedResults.Ok(_mapper.Map(model)); } [HttpPut("tiers/{id}")] public async Task, NotFound, ValidationProblem>> UpdateSubscriptionTierAsync(Guid id, [FromBody] ApiUpdateSubscriptionTierRequest tier) { var userId = User.GetUserId(); var existingTier = await _db.SubscriptionTiers.FirstOrDefaultAsync(t => t.Id == id && t.UserId == userId); if (existingTier is null) return TypedResults.NotFound($"No subscription tier found with id {id}"); if (existingTier.Level != tier.Level) { var levelExists = await _db.SubscriptionTiers.AnyAsync(t => t.UserId == userId && t.Level == tier.Level); if (levelExists) return TierLevelExistsProblem; } if (string.IsNullOrEmpty(tier.Name) == false) existingTier.Name = tier.Name; if (string.IsNullOrEmpty(tier.Description) == false) existingTier.Description = tier.Description; if (tier.Level.HasValue) existingTier.Level = tier.Level.Value; if (tier.MonthlyPrice.HasValue) existingTier.MonthlyPrice = tier.MonthlyPrice.Value; await _db.SaveChangesAsync(); return TypedResults.Ok(_mapper.Map(existingTier)); } [HttpDelete("tiers/{id}")] public async Task>> DeleteSubscriptionTierAsync(Guid id) { var userId = User.GetUserId(); var count = await _db.SubscriptionTiers .Where(t => t.Id == id && t.UserId == userId) .ExecuteDeleteAsync(); if (count == 0) return TypedResults.NotFound($"No subscription tier found with id {id}"); return TypedResults.Ok(); } } }