- first version of backend done
This commit is contained in:
parent
d466365348
commit
3c9f7323ba
5
OnlyPrompt.Backend/ApiModels/Category/Models.cs
Normal file
5
OnlyPrompt.Backend/ApiModels/Category/Models.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace OnlyPrompt.Backend.ApiModels.Category
|
||||
{
|
||||
public record ApiCategory(Guid Id, string Name, string Slug, string? Description);
|
||||
public record ApiMinimalCategory(string Name, string Slug);
|
||||
}
|
||||
5
OnlyPrompt.Backend/ApiModels/Category/Requests.cs
Normal file
5
OnlyPrompt.Backend/ApiModels/Category/Requests.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace OnlyPrompt.Backend.ApiModels.Category
|
||||
{
|
||||
public record ApiCreateCategoryRequest(string Name, string? Slug, string? Description);
|
||||
public record ApiUpdateCategoryRequest(string? Name, string? Slug, string? Description);
|
||||
}
|
||||
5
OnlyPrompt.Backend/ApiModels/Subscription/Models.cs
Normal file
5
OnlyPrompt.Backend/ApiModels/Subscription/Models.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace OnlyPrompt.Backend.ApiModels.Subscription
|
||||
{
|
||||
public record ApiSubscriptionTier(Guid Id, string Name, int Level, decimal MonthlyPrice, string? Description);
|
||||
public record ApiSubscription(Guid SubscribedToId, string SubscribedToName, ApiSubscriptionTier? CurrentTier);
|
||||
}
|
||||
5
OnlyPrompt.Backend/ApiModels/Subscription/Requests.cs
Normal file
5
OnlyPrompt.Backend/ApiModels/Subscription/Requests.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace OnlyPrompt.Backend.ApiModels.Subscription
|
||||
{
|
||||
public record ApiCreateSubscriptionTierRequest(string Name, decimal MonthlyPrice, int Level, string? Description);
|
||||
public record ApiUpdateSubscriptionTierRequest(string? Name, decimal? MonthlyPrice, int? Level, string? Description);
|
||||
}
|
||||
@ -31,7 +31,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<Results<RedirectHttpResult, BadRequest<string>, NotFound<string>>> LoginAsync([FromBody] ApiLoginRequest request)
|
||||
public async Task<Results<Ok, BadRequest<string>, NotFound<string>>> LoginAsync([FromBody] ApiLoginRequest request)
|
||||
{
|
||||
var user = await FindUserAsync(request.UserNameOrEmail);
|
||||
if (user is null)
|
||||
@ -46,7 +46,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
|
||||
var token = _jwtService.BuildToken(user, out var validUntil);
|
||||
this.Response.Cookies.Append("jwt", token, AuthCookieOptions.Copy(c => c.Expires = validUntil));
|
||||
return TypedResults.Redirect("feed");
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
@ -67,7 +67,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
return TypedResults.ValidationProblem(errors);
|
||||
}
|
||||
|
||||
var id = Guid.NewGuid();
|
||||
var id = Guid.CreateVersion7();
|
||||
var slug = await SlugHelper.GenerateUniqueSlugAsync(request.UserName, s => _db.UserProfiles.AnyAsync(up => up.Slug == s), 32);
|
||||
var avatarUrl = $"https://api.dicebear.com/9.x/bottts/svg?seed={id}";
|
||||
var newUser = new UserModel
|
||||
|
||||
123
OnlyPrompt.Backend/Controllers/CategoryController.cs
Normal file
123
OnlyPrompt.Backend/Controllers/CategoryController.cs
Normal file
@ -0,0 +1,123 @@
|
||||
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.Category;
|
||||
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/categories")]
|
||||
[Authorize(Roles = ModelConstants.AdminRole, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
public class CategoryController : BaseController
|
||||
{
|
||||
private static ValidationProblem SlugExistsProblem = TypedResults.ValidationProblem(new Dictionary<string, string[]>
|
||||
{
|
||||
{ nameof(CategoryModel.Slug), new[] { "Slug already exists." } }
|
||||
});
|
||||
|
||||
public CategoryController(OnlyPromptContext db, IMapper mapper) : base(db, mapper)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("minimal")]
|
||||
[Authorize(Roles = ModelConstants.UserRole)]
|
||||
public async Task<ApiMinimalCategory[]> GetMinimalCategoriesAsync()
|
||||
{
|
||||
var categories = await _db.Categories
|
||||
.ProjectTo<ApiMinimalCategory>(_mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Roles = ModelConstants.UserRole)]
|
||||
public async Task<ApiCategory[]> GetCategoriesAsync([Range(0, double.MaxValue)][FromQuery] int offset = 0, [Range(1, 100)][FromQuery] int limit = 20)
|
||||
{
|
||||
var categories = await _db.Categories
|
||||
.OrderBy(c => c.Id)
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.ProjectTo<ApiCategory>(_mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<Results<Ok<ApiCategory>, ValidationProblem>> CreateCategoryAsync([FromBody] ApiCreateCategoryRequest request)
|
||||
{
|
||||
var exists = await _db.Categories.AnyAsync(c => c.Slug == request.Slug);
|
||||
if (exists)
|
||||
return SlugExistsProblem;
|
||||
|
||||
var model = _mapper.Map<CategoryModel>(request);
|
||||
if (string.IsNullOrWhiteSpace(model.Slug))
|
||||
model.Slug = await SlugHelper.GenerateUniqueSlugAsync(request.Name, slug => _db.Categories.AnyAsync(c => c.Slug == slug), ModelConstants.MaxSlugLength);
|
||||
|
||||
_db.Categories.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
return TypedResults.Ok(_mapper.Map<ApiCategory>(model));
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<Results<Ok<ApiCategory>, NotFound<string>, ValidationProblem>> UpdateCategoryAsync(Identifier id, [FromBody] ApiUpdateCategoryRequest request)
|
||||
{
|
||||
var category = await _db.Categories.FindByIdentifierAsync(id);
|
||||
if (category is null)
|
||||
return TypedResults.NotFound("Category not found");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Name) == false)
|
||||
category.Name = request.Name;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(request.Description) == false)
|
||||
category.Description = request.Description;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Slug) == false && request.Slug != category.Slug)
|
||||
{
|
||||
var exists = await _db.Categories.AnyAsync(c => c.Slug == request.Slug && c.Id != category.Id);
|
||||
if (exists)
|
||||
return SlugExistsProblem;
|
||||
|
||||
category.Slug = request.Slug;
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return TypedResults.Ok(_mapper.Map<ApiCategory>(category));
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<Results<NoContent, BadRequest<string>, NotFound<string>>> DeleteCategoryAsync(Identifier id, [FromQuery] Identifier? replaceWith = null)
|
||||
{
|
||||
var hasPrompts = await _db.Prompts.AnyAsync(p => p.CategoryId == id.Id);
|
||||
if (hasPrompts)
|
||||
{
|
||||
if (replaceWith.HasValue == false)
|
||||
return TypedResults.BadRequest("Category has associated prompts. Provide a replacement category to reassign them to.");
|
||||
|
||||
var replacement = await _db.Categories.FindByIdentifierAsync(replaceWith.Value);
|
||||
if(replacement is null)
|
||||
return TypedResults.NotFound("Replacement category not found.");
|
||||
|
||||
await _db.Prompts.Where(p => p.CategoryId == id.Id)
|
||||
.ExecuteUpdateAsync(p => p.SetProperty(p => p.CategoryId, replacement.Id));
|
||||
}
|
||||
|
||||
var count = await _db.Categories.OfIdentifer(id)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
if (count == 0)
|
||||
return TypedResults.NotFound("Category not found");
|
||||
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ApiMinimalPrompt[]> GetFeedAsync(
|
||||
[FromQuery]int offset = 0,
|
||||
[Range(0, double.MaxValue)][FromQuery]int offset = 0,
|
||||
[Range(1, 100)][FromQuery]int limit = 20,
|
||||
[FromQuery]FeedSortType sortBy = FeedSortType.Date,
|
||||
[FromQuery]bool ascending = false,
|
||||
@ -32,7 +32,10 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var query = _db.Prompts
|
||||
.Where(x => x.Creator.Subscribers.Any(s => s.SubscriberId == userId));
|
||||
.Where(
|
||||
x => x.Creator.Subscribers.Any(s => s.SubscriberId == userId)
|
||||
&& x.CreatorId != userId
|
||||
);
|
||||
|
||||
if (category.HasValue)
|
||||
query = query.Where(x => category.Value.Id.HasValue ? x.CategoryId == category.Value.Id.Value : x.Category.Slug == category.Value.Slug);
|
||||
|
||||
@ -122,7 +122,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
}
|
||||
|
||||
[HttpPut("{id}/reviews")]
|
||||
public async Task<Results<Ok<ApiReview>, NotFound<string>>> AddReviewAsync(Identifier id, [FromBody] ApiCreateReviewRequest request)
|
||||
public async Task<Results<Ok<ApiReview>, BadRequest<string>, NotFound<string>>> AddReviewAsync(Identifier id, [FromBody] ApiCreateReviewRequest request)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var prompt = await GetAccessiblePrompts(userId!.Value)
|
||||
@ -132,6 +132,9 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
if (prompt is null)
|
||||
return TypedResults.NotFound("Prompt not found or no permission");
|
||||
|
||||
if(prompt.CreatorId == userId)
|
||||
return TypedResults.BadRequest("Cannot review your own prompt");
|
||||
|
||||
var review = await _db.Reviews.FirstOrDefaultAsync(
|
||||
r => r.PromptId == prompt.Id
|
||||
&& r.ReviewerId == userId
|
||||
|
||||
180
OnlyPrompt.Backend/Controllers/SubscriptionController.cs
Normal file
180
OnlyPrompt.Backend/Controllers/SubscriptionController.cs
Normal file
@ -0,0 +1,180 @@
|
||||
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<string, string[]>
|
||||
{
|
||||
{ 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<Results<Ok, BadRequest<string>, NotFound<string>>> 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<ApiSubscription[]> 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<ApiSubscription>(_mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
[HttpGet("{userId}")]
|
||||
public async Task<ApiSubscription?> 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<ApiSubscription>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
[HttpDelete("{userId}")]
|
||||
public async Task<Results<Ok, NotFound<string>>> 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<Results<Ok<ApiSubscriptionTier>, 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<SubscriptionTierModel>(tier);
|
||||
model.UserId = userId!.Value;
|
||||
_db.SubscriptionTiers.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
return TypedResults.Ok(_mapper.Map<ApiSubscriptionTier>(model));
|
||||
}
|
||||
|
||||
[HttpPut("tiers/{id}")]
|
||||
public async Task<Results<Ok<ApiSubscriptionTier>, NotFound<string>, 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<ApiSubscriptionTier>(existingTier));
|
||||
}
|
||||
|
||||
[HttpDelete("tiers/{id}")]
|
||||
public async Task<Results<Ok, NotFound<string>>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,14 +13,14 @@ namespace OnlyPrompt.Backend.Database.Models
|
||||
public Guid SubscribedToId { get; set; }
|
||||
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public required virtual UserModel SubscribedTo { get; set; }
|
||||
public virtual UserModel SubscribedTo { get; set; }
|
||||
|
||||
[Required]
|
||||
[ForeignKey(nameof(Subscriber))]
|
||||
public Guid SubscriberId { get; set; }
|
||||
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public required virtual UserModel Subscriber { get; set; }
|
||||
public virtual UserModel Subscriber { get; set; }
|
||||
|
||||
|
||||
[ForeignKey(nameof(SubscriptionTier))]
|
||||
|
||||
@ -19,7 +19,7 @@ namespace OnlyPrompt.Backend.Database.Models
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
[DeleteBehavior(DeleteBehavior.Cascade)]
|
||||
public required virtual UserModel User { get; set; }
|
||||
public virtual UserModel User { get; set; }
|
||||
|
||||
public decimal MonthlyPrice { get; set; }
|
||||
public int Level { get; set; }
|
||||
|
||||
@ -24,6 +24,7 @@ namespace OnlyPrompt.Backend.Database.Models
|
||||
public virtual IList<PromptModel> Prompts { get; set; } = new List<PromptModel>();
|
||||
public virtual IList<SubscriptionModel> Subscriptions { get; set; } = new List<SubscriptionModel>();
|
||||
public virtual IList<SubscriptionModel> Subscribers { get; set; } = new List<SubscriptionModel>();
|
||||
public virtual IList<SubscriptionTierModel> SubscriptionTiers { get; set; } = new List<SubscriptionTierModel>();
|
||||
|
||||
public bool IsLockoutEnabled { get; set; } = false;
|
||||
}
|
||||
|
||||
419
OnlyPrompt.Backend/Migrations/20260412002927_ReviewManyToMany.Designer.cs
generated
Normal file
419
OnlyPrompt.Backend/Migrations/20260412002927_ReviewManyToMany.Designer.cs
generated
Normal file
@ -0,0 +1,419 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using OnlyPrompt.Backend.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnlyPrompt.Backend.Migrations
|
||||
{
|
||||
[DbContext(typeof(OnlyPromptContext))]
|
||||
[Migration("20260412002927_ReviewManyToMany")]
|
||||
partial class ReviewManyToMany
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.5")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true)
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.CategoryModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("citext");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.PromptModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("Prompt")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("citext");
|
||||
|
||||
b.Property<Guid?>("SubscriptionTierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("SubscriptionTierId");
|
||||
|
||||
b.ToTable("Prompts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.ReviewModel", b =>
|
||||
{
|
||||
b.Property<Guid>("ReviewerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("PromptId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("ReviewerId", "PromptId");
|
||||
|
||||
b.HasIndex("PromptId");
|
||||
|
||||
b.ToTable("Reviews");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.SubscriptionModel", b =>
|
||||
{
|
||||
b.Property<Guid>("SubscriberId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SubscribedToId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("SubscriptionTierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("SubscriberId", "SubscribedToId");
|
||||
|
||||
b.HasIndex("SubscribedToId");
|
||||
|
||||
b.HasIndex("SubscriptionTierId");
|
||||
|
||||
b.ToTable("UserSubscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.SubscriptionTierModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("MonthlyPrice")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("Level", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SubscriptionTiers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.UserModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("citext");
|
||||
|
||||
b.Property<bool>("IsLockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.PrimitiveCollection<List<string>>("Roles")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("citext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.UserProfileModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Bio")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<bool>("IsPublic")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("citext");
|
||||
|
||||
b.Property<string>("Specialities")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("UserProfiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.PromptModel", b =>
|
||||
{
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.CategoryModel", "Category")
|
||||
.WithMany("Prompts")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "Creator")
|
||||
.WithMany("Prompts")
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.SubscriptionTierModel", "SubscriptionTier")
|
||||
.WithMany("Prompts")
|
||||
.HasForeignKey("SubscriptionTierId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("SubscriptionTier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.ReviewModel", b =>
|
||||
{
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.PromptModel", "Prompt")
|
||||
.WithMany("Reviews")
|
||||
.HasForeignKey("PromptId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "Reviewer")
|
||||
.WithMany()
|
||||
.HasForeignKey("ReviewerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Prompt");
|
||||
|
||||
b.Navigation("Reviewer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.SubscriptionModel", b =>
|
||||
{
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "SubscribedTo")
|
||||
.WithMany("Subscribers")
|
||||
.HasForeignKey("SubscribedToId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "Subscriber")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("SubscriberId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.SubscriptionTierModel", "SubscriptionTier")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("SubscriptionTierId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("SubscribedTo");
|
||||
|
||||
b.Navigation("Subscriber");
|
||||
|
||||
b.Navigation("SubscriptionTier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.SubscriptionTierModel", b =>
|
||||
{
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.UserProfileModel", b =>
|
||||
{
|
||||
b.HasOne("OnlyPrompt.Backend.Database.Models.UserModel", "User")
|
||||
.WithOne("Profile")
|
||||
.HasForeignKey("OnlyPrompt.Backend.Database.Models.UserProfileModel", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.CategoryModel", b =>
|
||||
{
|
||||
b.Navigation("Prompts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.PromptModel", b =>
|
||||
{
|
||||
b.Navigation("Reviews");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.SubscriptionTierModel", b =>
|
||||
{
|
||||
b.Navigation("Prompts");
|
||||
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.UserModel", b =>
|
||||
{
|
||||
b.Navigation("Profile")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Prompts");
|
||||
|
||||
b.Navigation("Subscribers");
|
||||
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnlyPrompt.Backend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReviewManyToMany : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Reviews",
|
||||
table: "Reviews");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Reviews_ReviewerId_PromptId",
|
||||
table: "Reviews");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Id",
|
||||
table: "Reviews");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Content",
|
||||
table: "Prompts");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "Prompts",
|
||||
type: "character varying(1000)",
|
||||
maxLength: 1000,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Prompt",
|
||||
table: "Prompts",
|
||||
type: "character varying(4000)",
|
||||
maxLength: 4000,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Reviews",
|
||||
table: "Reviews",
|
||||
columns: new[] { "ReviewerId", "PromptId" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Reviews",
|
||||
table: "Reviews");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Description",
|
||||
table: "Prompts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Prompt",
|
||||
table: "Prompts");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "Reviews",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Content",
|
||||
table: "Prompts",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Reviews",
|
||||
table: "Reviews",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Reviews_ReviewerId_PromptId",
|
||||
table: "Reviews",
|
||||
columns: new[] { "ReviewerId", "PromptId" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
@ -67,16 +68,22 @@ namespace OnlyPrompt.Backend.Migrations
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("Prompt")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
@ -109,8 +116,10 @@ namespace OnlyPrompt.Backend.Migrations
|
||||
|
||||
modelBuilder.Entity("OnlyPrompt.Backend.Database.Models.ReviewModel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
b.Property<Guid>("ReviewerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("PromptId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
@ -120,25 +129,16 @@ namespace OnlyPrompt.Backend.Migrations
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("PromptId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ReviewerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
b.HasKey("ReviewerId", "PromptId");
|
||||
|
||||
b.HasIndex("PromptId");
|
||||
|
||||
b.HasIndex("ReviewerId", "PromptId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Reviews");
|
||||
});
|
||||
|
||||
@ -221,7 +221,7 @@ namespace OnlyPrompt.Backend.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.PrimitiveCollection<string[]>("Roles")
|
||||
b.PrimitiveCollection<List<string>>("Roles")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
|
||||
@ -2,15 +2,18 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Identity.Web;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using OnlyPrompt.Backend.Database;
|
||||
using OnlyPrompt.Backend.Database.Models;
|
||||
using OnlyPrompt.Backend.Services.Jwt;
|
||||
using OnlyPrompt.Backend.Utils;
|
||||
using Scalar.AspNetCore;
|
||||
using System.Text;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var config = builder.Configuration;
|
||||
// Add services to the container.
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
|
||||
builder.Services.AddDbContext<OnlyPromptContext>(opts =>
|
||||
@ -26,6 +29,16 @@ builder.Services.AddAutoMapper(AutoMapperSetup.Setup);
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opts => {
|
||||
opts.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = config["Jwt:Issuer"],
|
||||
ValidAudience = config["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]))
|
||||
};
|
||||
opts.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
@ -56,4 +69,8 @@ app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
using var scope = app.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<OnlyPromptContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
|
||||
app.Run();
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using AutoMapper;
|
||||
using OnlyPrompt.Backend.ApiModels.Auth;
|
||||
using OnlyPrompt.Backend.ApiModels.Category;
|
||||
using OnlyPrompt.Backend.ApiModels.Prompt;
|
||||
using OnlyPrompt.Backend.ApiModels.Subscription;
|
||||
using OnlyPrompt.Backend.ApiModels.UserProfile;
|
||||
using OnlyPrompt.Backend.Database.Models;
|
||||
|
||||
@ -45,13 +47,47 @@ namespace OnlyPrompt.Backend.Utils
|
||||
.MapCtorParamFrom(x => x.CreatorId, x => x.CreatorId)
|
||||
.MapCtorParamFrom(x => x.TierLevel, x => x.SubscriptionTier == null ? (int?)null : x.SubscriptionTier.Level)
|
||||
.MapCtorParamFrom(x => x.TierName, x => x.SubscriptionTier == null ? null : x.SubscriptionTier.Name)
|
||||
.MapCtorParamFrom(x => x.AverageRating, x => x.Reviews.Average(r => (double?)r.Rating));
|
||||
.MapCtorParamFrom(x => x.AverageRating, x => x.Reviews.Average(r => (double?)r.Rating))
|
||||
.MapCtorParamFrom(x => x.CanAccess, x => true);
|
||||
|
||||
config.CreateMap<ReviewModel, ApiReview>()
|
||||
.MapCtorParamFrom(x => x.CreatorId, x => x.ReviewerId)
|
||||
.MapCtorParamFrom(x => x.CreatorName, x => x.Reviewer.Profile.DisplayName)
|
||||
.MapCtorParamFrom(x => x.Comment, x => x.Comment)
|
||||
.MapCtorParamFrom(x => x.Rating, x => x.Rating);
|
||||
|
||||
config.CreateMap<CategoryModel, ApiCategory>()
|
||||
.MapCtorParamFrom(x => x.Id, x => x.Id)
|
||||
.MapCtorParamFrom(x => x.Name, x => x.Name)
|
||||
.MapCtorParamFrom(x => x.Description, x => x.Description)
|
||||
.MapCtorParamFrom(x => x.Slug, x => x.Slug);
|
||||
|
||||
config.CreateMap<CategoryModel, ApiMinimalCategory>()
|
||||
.MapCtorParamFrom(x => x.Name, x => x.Name)
|
||||
.MapCtorParamFrom(x => x.Slug, x => x.Slug);
|
||||
|
||||
config.CreateMap<ApiCreateCategoryRequest, CategoryModel>()
|
||||
.MapMemberFrom(x => x.Description, x => x.Description)
|
||||
.MapMemberFrom(x => x.Name, x => x.Name)
|
||||
.MapMemberFrom(x => x.Slug, x => x.Slug);
|
||||
|
||||
config.CreateMap<SubscriptionTierModel, ApiSubscriptionTier>()
|
||||
.MapCtorParamFrom(x => x.Id, x => x.Id)
|
||||
.MapCtorParamFrom(x => x.Name, x => x.Name)
|
||||
.MapCtorParamFrom(x => x.Level, x => x.Level)
|
||||
.MapCtorParamFrom(x => x.MonthlyPrice, x => x.MonthlyPrice)
|
||||
.MapCtorParamFrom(x => x.Description, x => x.Description);
|
||||
|
||||
config.CreateMap<ApiCreateSubscriptionTierRequest, SubscriptionTierModel>()
|
||||
.MapMemberFrom(x => x.Name, x => x.Name)
|
||||
.MapMemberFrom(x => x.Level, x => x.Level)
|
||||
.MapMemberFrom(x => x.MonthlyPrice, x => x.MonthlyPrice)
|
||||
.MapMemberFrom(x => x.Description, x => x.Description);
|
||||
|
||||
config.CreateMap<SubscriptionModel, ApiSubscription>()
|
||||
.MapCtorParamFrom(x => x.SubscribedToId, x => x.SubscribedToId)
|
||||
.MapCtorParamFrom(x => x.SubscribedToName, x => x.SubscribedTo.Profile.DisplayName)
|
||||
.MapCtorParamFrom(x => x.CurrentTier, x => x.SubscriptionTier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,16 +16,27 @@ namespace OnlyPrompt.Backend.Utils
|
||||
slug = InvalidCharacters.Replace(slug, string.Empty);
|
||||
slug = MultipleDashes.Replace(slug, "-");
|
||||
slug = slug.Trim('-');
|
||||
if (maxLength.HasValue)
|
||||
slug = slug.Limit(maxLength.Value);
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
private const string SuffixChars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
public static async Task<string> GenerateUniqueSlugAsync(string input, Func<string, Task<bool>> existsFunc, int? maxLenght)
|
||||
{
|
||||
var baseSlug = GenerateSlug(input, maxLenght - 9);
|
||||
var slug = baseSlug;
|
||||
var suffix = Random.Shared.GetString(8, SuffixChars);
|
||||
return $"{slug}-{suffix}";
|
||||
var slug = GenerateSlug(input);
|
||||
var exists = await existsFunc(slug);
|
||||
if (exists)
|
||||
{
|
||||
var suffix = Random.Shared.GetString(8, SuffixChars);
|
||||
if (maxLenght.HasValue)
|
||||
slug = slug.Limit(maxLenght.Value - 9);
|
||||
|
||||
slug = $"{slug}-{suffix}";
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Include Error Detail=true;User ID=onlyprompt;Password=onlyprompt;Host=postgres;Port=1803;Database=onlyprompt;Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"
|
||||
"DefaultConnection": "Include Error Detail=true;User ID=onlyprompt;Password=onlyprompt;Host=localhost;Port=1803;Database=onlyprompt;Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"
|
||||
},
|
||||
"Jwt": {
|
||||
"Issuer": "https://onlyprompts.com",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user