212 lines
6.1 KiB
C#
212 lines
6.1 KiB
C#
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Rewrite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.Extensions.FileProviders;
|
|
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;
|
|
using System.Text.Json;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
var config = builder.Configuration;
|
|
// Add services to the container.
|
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
|
|
builder.Services.AddDbContext<OnlyPromptContext>(opts =>
|
|
{
|
|
opts.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
|
|
opts.UseLazyLoadingProxies();
|
|
opts.ConfigureWarnings(warnings =>
|
|
warnings.Ignore(RelationalEventId.PendingModelChangesWarning));
|
|
});
|
|
|
|
builder.Services.AddSingleton<IPasswordHasher<UserModel>, PasswordHasher<UserModel>>();
|
|
builder.Services.AddSingleton<ITokenService, JwtTokenService>();
|
|
builder.Services.AddAutoMapper(AutoMapperSetup.Setup);
|
|
builder.Services.AddValidation(opts =>
|
|
{
|
|
opts.MaxDepth = 10;
|
|
});
|
|
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 =>
|
|
{
|
|
if (context.Request.Cookies.ContainsKey("jwt"))
|
|
context.Token = context.Request.Cookies["jwt"];
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
};
|
|
});
|
|
|
|
builder.Services.AddControllers().AddJsonOptions(jsonOpts =>
|
|
{
|
|
jsonOpts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
|
});
|
|
builder.Services.AddOpenApi(opts => opts.AddScalarTransformers());
|
|
|
|
var app = builder.Build();
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.MapOpenApi();
|
|
app.MapScalarApiReference();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
|
|
var rewrite = new RewriteOptions()
|
|
.AddRewrite(@"^(?!scalar\/?|api\/?)([^.]+)$", "$1.html", skipRemainingRules: true);
|
|
|
|
app.UseRewriter(rewrite);
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
var dir = Path.GetFullPath("./../OnlyPrompt.Frontend");
|
|
app.UseStaticFiles(new StaticFileOptions
|
|
{
|
|
FileProvider = new PhysicalFileProvider(dir),
|
|
RedirectToAppendTrailingSlash = true,
|
|
HttpsCompression = HttpsCompressionMode.Compress,
|
|
});
|
|
}
|
|
else
|
|
{
|
|
app.UseStaticFiles();
|
|
}
|
|
|
|
app.MapControllers();
|
|
app.MapGet("/", async (HttpContext context) =>
|
|
{
|
|
var authResult = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
|
|
return authResult.Succeeded
|
|
? Results.Redirect("/dashboard")
|
|
: Results.Redirect("/login");
|
|
});
|
|
app.MapFallbackToFile("/login.html");
|
|
using var scope = app.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<OnlyPromptContext>();
|
|
await db.Database.MigrateAsync();
|
|
await EnsurePromptDetailColumnsAsync(db);
|
|
await EnsurePromptLikesTableAsync(db);
|
|
await EnsurePromptSavesTableAsync(db);
|
|
await SeedDefaultCategoriesAsync(db);
|
|
|
|
app.Run();
|
|
|
|
static async Task EnsurePromptDetailColumnsAsync(OnlyPromptContext db)
|
|
{
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE "Prompts"
|
|
ADD COLUMN IF NOT EXISTS "ExampleImageUrl" text;
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE "Prompts"
|
|
ADD COLUMN IF NOT EXISTS "ExampleOutput" text;
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE "Prompts"
|
|
ADD COLUMN IF NOT EXISTS "Price" numeric;
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE "Prompts"
|
|
ALTER COLUMN "Prompt" TYPE text;
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE "Prompts"
|
|
ALTER COLUMN "ExampleOutput" TYPE text;
|
|
""");
|
|
}
|
|
|
|
static async Task EnsurePromptLikesTableAsync(OnlyPromptContext db)
|
|
{
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
CREATE TABLE IF NOT EXISTS "PromptLikes" (
|
|
"PromptId" uuid NOT NULL,
|
|
"UserId" uuid NOT NULL,
|
|
CONSTRAINT "PK_PromptLikes" PRIMARY KEY ("PromptId", "UserId"),
|
|
CONSTRAINT "FK_PromptLikes_Prompts_PromptId" FOREIGN KEY ("PromptId") REFERENCES "Prompts" ("Id") ON DELETE CASCADE,
|
|
CONSTRAINT "FK_PromptLikes_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
|
|
);
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
CREATE INDEX IF NOT EXISTS "IX_PromptLikes_UserId" ON "PromptLikes" ("UserId");
|
|
""");
|
|
}
|
|
|
|
static async Task EnsurePromptSavesTableAsync(OnlyPromptContext db)
|
|
{
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
CREATE TABLE IF NOT EXISTS "PromptSaves" (
|
|
"PromptId" uuid NOT NULL,
|
|
"UserId" uuid NOT NULL,
|
|
CONSTRAINT "PK_PromptSaves" PRIMARY KEY ("PromptId", "UserId"),
|
|
CONSTRAINT "FK_PromptSaves_Prompts_PromptId" FOREIGN KEY ("PromptId") REFERENCES "Prompts" ("Id") ON DELETE CASCADE,
|
|
CONSTRAINT "FK_PromptSaves_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
|
|
);
|
|
""");
|
|
|
|
await db.Database.ExecuteSqlRawAsync("""
|
|
CREATE INDEX IF NOT EXISTS "IX_PromptSaves_UserId" ON "PromptSaves" ("UserId");
|
|
""");
|
|
}
|
|
|
|
static async Task SeedDefaultCategoriesAsync(OnlyPromptContext db)
|
|
{
|
|
var defaults = new[]
|
|
{
|
|
("creative-writing", "Creative Writing"),
|
|
("coding", "Coding"),
|
|
("art", "Art"),
|
|
("marketing", "Marketing"),
|
|
("video", "Video"),
|
|
("data", "Data")
|
|
};
|
|
|
|
foreach (var (slug, name) in defaults)
|
|
{
|
|
if (await db.Categories.AnyAsync(c => c.Slug == slug))
|
|
continue;
|
|
|
|
db.Categories.Add(new CategoryModel
|
|
{
|
|
Id = Guid.CreateVersion7(),
|
|
Slug = slug,
|
|
Name = name,
|
|
Description = $"{name} prompts"
|
|
});
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
}
|