feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Build output — rebuilt inside container
|
||||
**/bin/
|
||||
**/obj/
|
||||
|
||||
# Local dev secrets
|
||||
**/appsettings.Development.json
|
||||
**/appsettings.*.Local.json
|
||||
**/*.user
|
||||
|
||||
# IDE / OS
|
||||
.vs/
|
||||
.idea/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker files
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose*.yml
|
||||
@@ -0,0 +1,24 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
# The .NET base image ships neither wget nor curl, which the container healthcheck needs.
|
||||
# Copy a single static busybox binary named `wget` (busybox dispatches on argv[0]).
|
||||
# This stays fully offline — no apt/network — matching the vendored Go builds.
|
||||
COPY --from=busybox:1.36 /bin/busybox /usr/bin/wget
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
# Restore is its own cached layer: it only re-runs when the .csproj (deps) changes,
|
||||
# not on every source edit. Critical here — NuGet restore is the slow step.
|
||||
COPY NuGet.Config .
|
||||
COPY ["FlatRender.ContentSvc/FlatRender.ContentSvc.csproj", "FlatRender.ContentSvc/"]
|
||||
RUN dotnet restore "FlatRender.ContentSvc/FlatRender.ContentSvc.csproj"
|
||||
COPY . .
|
||||
# Single publish compiles + packages; --no-restore reuses the cached restore above.
|
||||
RUN dotnet publish "FlatRender.ContentSvc/FlatRender.ContentSvc.csproj" \
|
||||
-c Release -o /app/publish --no-restore /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
ENTRYPOINT ["dotnet", "FlatRender.ContentSvc.dll"]
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="FlatRender.ContentSvc/FlatRender.ContentSvc.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,279 @@
|
||||
using FlatRender.ContentSvc.Domain.Entities;
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
using FlatRender.ContentSvc.Infrastructure.Data;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using FlatRender.ContentSvc.Models.Responses;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FlatRender.ContentSvc.Application.Services;
|
||||
|
||||
public class CmsService(ContentDbContext db)
|
||||
{
|
||||
// ── Blogs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<PagedResponse<BlogSummaryResponse>> GetBlogsAsync(BlogListRequest req)
|
||||
{
|
||||
var kind = Enum.TryParse<BlogKind>(req.Kind, true, out var k) ? k : BlogKind.Blog;
|
||||
var q = db.Blogs.Where(x => x.Kind == kind);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Search))
|
||||
q = q.Where(x => EF.Functions.ILike(x.Title, $"%{req.Search}%"));
|
||||
|
||||
if (req.IsPublished.HasValue)
|
||||
q = q.Where(x => x.IsPublished == req.IsPublished);
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.OrderByDescending(x => x.PublishDate ?? x.CreatedAt)
|
||||
.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<BlogSummaryResponse>(
|
||||
items.Select(MapBlogSummary),
|
||||
new PaginationMeta(req.Page, req.PageSize, total, (int)Math.Ceiling((double)total / req.PageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<BlogDetailResponse> GetBlogBySlugAsync(string slug)
|
||||
{
|
||||
var blog = await db.Blogs.FirstOrDefaultAsync(x => x.Slug == slug)
|
||||
?? throw new KeyNotFoundException($"Blog '{slug}' not found");
|
||||
await db.Blogs.Where(x => x.Id == blog.Id)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.ViewCount, x => x.ViewCount + 1));
|
||||
return MapBlogDetail(blog);
|
||||
}
|
||||
|
||||
public async Task<BlogDetailResponse> CreateBlogAsync(BlogListRequest _, CreateBlogRequest req, Guid? authorId)
|
||||
{
|
||||
var kind = Enum.TryParse<BlogKind>(req.Kind, true, out var k) ? k : BlogKind.Blog;
|
||||
var blog = new Blog
|
||||
{
|
||||
TenantId = null, Kind = kind, Slug = req.Slug, Title = req.Title,
|
||||
ShortDescription = req.ShortDescription, Content = req.Content,
|
||||
MetaTitle = req.MetaTitle, MetaDescription = req.MetaDescription, MetaKeywords = req.MetaKeywords,
|
||||
IncludeInSiteMap = req.IncludeInSiteMap, Image = req.Image, Cover = req.Cover,
|
||||
AuthorUserId = authorId, AuthorDisplayName = req.AuthorDisplayName,
|
||||
IsPublished = req.IsPublished, PublishDate = req.PublishDate
|
||||
};
|
||||
db.Blogs.Add(blog);
|
||||
await db.SaveChangesAsync();
|
||||
return MapBlogDetail(blog);
|
||||
}
|
||||
|
||||
public async Task<BlogDetailResponse> UpdateBlogAsync(Guid id, UpdateBlogRequest req)
|
||||
{
|
||||
var blog = await db.Blogs.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Blog {id} not found");
|
||||
blog.Slug = req.Slug; blog.Title = req.Title; blog.ShortDescription = req.ShortDescription;
|
||||
blog.Content = req.Content; blog.MetaTitle = req.MetaTitle; blog.MetaDescription = req.MetaDescription;
|
||||
blog.MetaKeywords = req.MetaKeywords; blog.IncludeInSiteMap = req.IncludeInSiteMap;
|
||||
blog.Image = req.Image; blog.Cover = req.Cover; blog.AuthorDisplayName = req.AuthorDisplayName;
|
||||
blog.IsPublished = req.IsPublished; blog.PublishDate = req.PublishDate;
|
||||
blog.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return MapBlogDetail(blog);
|
||||
}
|
||||
|
||||
public async Task DeleteBlogAsync(Guid id)
|
||||
{
|
||||
var blog = await db.Blogs.FindAsync(id) ?? throw new KeyNotFoundException($"Blog {id} not found");
|
||||
blog.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Comments ──────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<PagedResponse<CommentResponse>> GetCommentsAsync(
|
||||
int page, int pageSize, Guid? blogId, Guid? containerId, bool? isApproved)
|
||||
{
|
||||
var q = db.Comments.AsQueryable();
|
||||
if (blogId.HasValue) q = q.Where(x => x.BlogId == blogId);
|
||||
if (containerId.HasValue) q = q.Where(x => x.ContainerId == containerId);
|
||||
if (isApproved.HasValue) q = q.Where(x => x.IsApproved == isApproved);
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.OrderByDescending(x => x.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<CommentResponse>(
|
||||
items.Select(MapComment),
|
||||
new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<CommentResponse> CreateCommentAsync(CreateCommentRequest req, Guid userId)
|
||||
{
|
||||
if (req.BlogId == null && req.ContainerId == null)
|
||||
throw new ArgumentException("Either BlogId or ContainerId must be provided");
|
||||
if (req.BlogId != null && req.ContainerId != null)
|
||||
throw new ArgumentException("Only one of BlogId or ContainerId may be provided");
|
||||
|
||||
var comment = new Comment
|
||||
{
|
||||
UserId = userId, BlogId = req.BlogId, ContainerId = req.ContainerId,
|
||||
ParentCommentId = req.ParentCommentId, Content = req.Content, Rate = req.Rate
|
||||
};
|
||||
db.Comments.Add(comment);
|
||||
await db.SaveChangesAsync();
|
||||
return MapComment(comment);
|
||||
}
|
||||
|
||||
public async Task ApproveCommentAsync(Guid id, bool approve)
|
||||
{
|
||||
var comment = await db.Comments.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Comment {id} not found");
|
||||
comment.IsApproved = approve;
|
||||
comment.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteCommentAsync(Guid id)
|
||||
{
|
||||
var comment = await db.Comments.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Comment {id} not found");
|
||||
comment.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Slides ────────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<SlideResponse>> GetSlidesAsync(Guid? tenantId)
|
||||
{
|
||||
var q = db.NewSlides.Where(x => x.IsActive &&
|
||||
(x.TenantId == null || x.TenantId == tenantId) &&
|
||||
(x.ExpireDate == null || x.ExpireDate > DateTime.UtcNow));
|
||||
|
||||
return await q.OrderBy(x => x.Sort).Select(s => new SlideResponse(
|
||||
s.Id, s.Keyword, s.Title, s.Image, s.Parameter, s.SlideType.ToString(),
|
||||
s.ExpireDate, s.Sort, s.IsActive
|
||||
)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<SlideResponse> CreateSlideAsync(CreateSlideRequest req)
|
||||
{
|
||||
if (!Enum.TryParse<SlideType>(req.SlideType, true, out var type))
|
||||
throw new ArgumentException($"Invalid SlideType: {req.SlideType}");
|
||||
|
||||
var slide = new NewSlide
|
||||
{
|
||||
Keyword = req.Keyword, Title = req.Title, Image = req.Image,
|
||||
Parameter = req.Parameter, SlideType = type, ExpireDate = req.ExpireDate,
|
||||
Sort = req.Sort, IsActive = req.IsActive
|
||||
};
|
||||
db.NewSlides.Add(slide);
|
||||
await db.SaveChangesAsync();
|
||||
return new SlideResponse(slide.Id, slide.Keyword, slide.Title, slide.Image, slide.Parameter,
|
||||
slide.SlideType.ToString(), slide.ExpireDate, slide.Sort, slide.IsActive);
|
||||
}
|
||||
|
||||
public async Task DeleteSlideAsync(Guid id)
|
||||
{
|
||||
var slide = await db.NewSlides.FindAsync(id) ?? throw new KeyNotFoundException($"Slide {id} not found");
|
||||
db.NewSlides.Remove(slide);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Home Page Events ──────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<HomePageEventResponse>> GetHomePageEventsAsync(Guid? tenantId)
|
||||
{
|
||||
return await db.HomePageEvents
|
||||
.Where(x => x.IsActive && (x.TenantId == null || x.TenantId == tenantId))
|
||||
.OrderBy(x => x.Sort)
|
||||
.Select(e => new HomePageEventResponse(
|
||||
e.Id, e.Title, e.Subtitle, e.Description, e.Badge, e.BadgeClass,
|
||||
e.ButtonText, e.ButtonUrl, e.ButtonClass, e.Color, e.BackgroundColor,
|
||||
e.TextColor, e.Image, e.IsActive, e.Sort, e.StartsAt, e.EndsAt
|
||||
)).ToListAsync();
|
||||
}
|
||||
|
||||
// ── Website Settings ──────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<WebsiteSettingResponse>> GetSettingsAsync(Guid? tenantId, bool includeSecret = false)
|
||||
{
|
||||
var q = db.WebsiteSettings.Where(x => x.TenantId == tenantId);
|
||||
if (!includeSecret) q = q.Where(x => !x.IsSecret);
|
||||
return await q.Select(s => new WebsiteSettingResponse(s.Id, s.Key, s.Value, s.Description, s.IsSecret)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<WebsiteSettingResponse> UpsertSettingAsync(Guid? tenantId, UpsertWebsiteSettingRequest req)
|
||||
{
|
||||
var setting = await db.WebsiteSettings.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Key == req.Key);
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new WebsiteSetting { TenantId = tenantId, Key = req.Key };
|
||||
db.WebsiteSettings.Add(setting);
|
||||
}
|
||||
setting.Value = req.Value;
|
||||
setting.Description = req.Description;
|
||||
setting.IsSecret = req.IsSecret;
|
||||
setting.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return new WebsiteSettingResponse(setting.Id, setting.Key, setting.Value, setting.Description, setting.IsSecret);
|
||||
}
|
||||
|
||||
// ── Favorites ─────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<FavoriteFolderResponse>> GetFavoriteFoldersAsync(Guid userId)
|
||||
{
|
||||
return await db.FavoriteFolders
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(f => new FavoriteFolderResponse(
|
||||
f.Id, f.Name, f.Description,
|
||||
db.FavoriteContainers.Count(fc => fc.FolderId == f.Id),
|
||||
f.CreatedAt
|
||||
)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<FavoriteFolderResponse> CreateFavoriteFolderAsync(Guid userId, Guid tenantId, CreateFavoriteFolderRequest req)
|
||||
{
|
||||
var folder = new FavoriteFolder { UserId = userId, TenantId = tenantId, Name = req.Name, Description = req.Description };
|
||||
db.FavoriteFolders.Add(folder);
|
||||
await db.SaveChangesAsync();
|
||||
return new FavoriteFolderResponse(folder.Id, folder.Name, folder.Description, 0, folder.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task DeleteFavoriteFolderAsync(Guid userId, Guid id)
|
||||
{
|
||||
var folder = await db.FavoriteFolders.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId)
|
||||
?? throw new KeyNotFoundException($"Folder {id} not found");
|
||||
db.FavoriteFolders.Remove(folder);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task AddFavoriteContainerAsync(Guid userId, Guid tenantId, AddFavoriteContainerRequest req)
|
||||
{
|
||||
var exists = await db.FavoriteContainers.AnyAsync(x => x.UserId == userId && x.ContainerId == req.ContainerId);
|
||||
if (exists) return;
|
||||
|
||||
db.FavoriteContainers.Add(new FavoriteContainer
|
||||
{
|
||||
UserId = userId, TenantId = tenantId, ContainerId = req.ContainerId,
|
||||
FolderId = req.FolderId, Note = req.Note
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveFavoriteContainerAsync(Guid userId, Guid containerId)
|
||||
{
|
||||
var fav = await db.FavoriteContainers.FirstOrDefaultAsync(x => x.UserId == userId && x.ContainerId == containerId)
|
||||
?? throw new KeyNotFoundException($"Favorite not found");
|
||||
db.FavoriteContainers.Remove(fav);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Mappers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static BlogSummaryResponse MapBlogSummary(Blog b) => new(
|
||||
b.Id, b.Slug, b.Title, b.ShortDescription, b.Image, b.Cover,
|
||||
b.AuthorDisplayName, b.IsPublished, b.PublishDate, b.ViewCount, b.CreatedAt
|
||||
);
|
||||
|
||||
private static BlogDetailResponse MapBlogDetail(Blog b) => new(
|
||||
b.Id, b.Slug, b.Title, b.ShortDescription, b.Content, b.MetaTitle, b.MetaDescription,
|
||||
b.MetaKeywords, b.IncludeInSiteMap, b.Image, b.Cover, b.AuthorUserId, b.AuthorDisplayName,
|
||||
b.IsPublished, b.PublishDate, b.ViewCount, b.CreatedAt, b.UpdatedAt
|
||||
);
|
||||
|
||||
private static CommentResponse MapComment(Comment c) => new(
|
||||
c.Id, c.UserId, c.BlogId, c.ContainerId, c.ParentCommentId,
|
||||
c.Content, c.Rate, c.IsApproved, c.IsPinned, c.CreatedAt
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using FlatRender.ContentSvc.Domain.Entities;
|
||||
using FlatRender.ContentSvc.Infrastructure.Data;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using FlatRender.ContentSvc.Models.Responses;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FlatRender.ContentSvc.Application.Services;
|
||||
|
||||
public class TaxonomyService(ContentDbContext db)
|
||||
{
|
||||
public async Task<List<CategoryResponse>> GetCategoryTreeAsync()
|
||||
{
|
||||
var all = await db.Categories
|
||||
.Where(x => x.ParentId == null)
|
||||
.Include(x => x.Children)
|
||||
.OrderBy(x => x.Sort)
|
||||
.ToListAsync();
|
||||
|
||||
return all.Select(MapCategory).ToList();
|
||||
}
|
||||
|
||||
public async Task<CategoryResponse> CreateCategoryAsync(CreateCategoryRequest req)
|
||||
{
|
||||
var cat = new Category
|
||||
{
|
||||
ParentId = req.ParentId,
|
||||
Name = req.Name,
|
||||
Slug = req.Slug,
|
||||
Description = req.Description,
|
||||
ImageUrl = req.ImageUrl,
|
||||
Icon = req.Icon,
|
||||
MetaTitle = req.MetaTitle,
|
||||
MetaDescription = req.MetaDescription,
|
||||
MetaKeywords = req.MetaKeywords,
|
||||
BotFollow = req.BotFollow,
|
||||
Sort = req.Sort,
|
||||
IsActive = req.IsActive
|
||||
};
|
||||
db.Categories.Add(cat);
|
||||
await db.SaveChangesAsync();
|
||||
return MapCategory(cat);
|
||||
}
|
||||
|
||||
public async Task<CategoryResponse> UpdateCategoryAsync(Guid id, UpdateCategoryRequest req)
|
||||
{
|
||||
var cat = await db.Categories.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Category {id} not found");
|
||||
|
||||
cat.ParentId = req.ParentId;
|
||||
cat.Name = req.Name;
|
||||
cat.Slug = req.Slug;
|
||||
cat.Description = req.Description;
|
||||
cat.ImageUrl = req.ImageUrl;
|
||||
cat.Icon = req.Icon;
|
||||
cat.MetaTitle = req.MetaTitle;
|
||||
cat.MetaDescription = req.MetaDescription;
|
||||
cat.MetaKeywords = req.MetaKeywords;
|
||||
cat.BotFollow = req.BotFollow;
|
||||
cat.Sort = req.Sort;
|
||||
cat.IsActive = req.IsActive;
|
||||
cat.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return MapCategory(cat);
|
||||
}
|
||||
|
||||
public async Task DeleteCategoryAsync(Guid id)
|
||||
{
|
||||
var cat = await db.Categories.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Category {id} not found");
|
||||
cat.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<TagResponse>> GetTagsAsync(int page, int pageSize, string? search)
|
||||
{
|
||||
var q = db.Tags.AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%"));
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.OrderBy(x => x.Name).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<TagResponse>(
|
||||
items.Select(t => new TagResponse(t.Id, t.Name, t.LatinName, t.Slug, t.AppliesToMode, t.IsActive)),
|
||||
new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<TagResponse> CreateTagAsync(CreateTagRequest req)
|
||||
{
|
||||
var tag = new Tag
|
||||
{
|
||||
Name = req.Name,
|
||||
LatinName = req.LatinName,
|
||||
Slug = req.Slug,
|
||||
AppliesToMode = req.AppliesToMode,
|
||||
IsActive = req.IsActive
|
||||
};
|
||||
db.Tags.Add(tag);
|
||||
await db.SaveChangesAsync();
|
||||
return new TagResponse(tag.Id, tag.Name, tag.LatinName, tag.Slug, tag.AppliesToMode, tag.IsActive);
|
||||
}
|
||||
|
||||
public async Task<TagResponse> UpdateTagAsync(Guid id, UpdateTagRequest req)
|
||||
{
|
||||
var tag = await db.Tags.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Tag {id} not found");
|
||||
tag.Name = req.Name;
|
||||
tag.LatinName = req.LatinName;
|
||||
tag.Slug = req.Slug;
|
||||
tag.AppliesToMode = req.AppliesToMode;
|
||||
tag.IsActive = req.IsActive;
|
||||
tag.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return new TagResponse(tag.Id, tag.Name, tag.LatinName, tag.Slug, tag.AppliesToMode, tag.IsActive);
|
||||
}
|
||||
|
||||
public async Task DeleteTagAsync(Guid id)
|
||||
{
|
||||
var tag = await db.Tags.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Tag {id} not found");
|
||||
tag.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<FontResponse>> GetFontsAsync(int page, int pageSize, string? search, string? direction)
|
||||
{
|
||||
var q = db.Fonts.AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%"));
|
||||
if (!string.IsNullOrWhiteSpace(direction))
|
||||
q = q.Where(x => x.Direction == direction);
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.OrderBy(x => x.Sort).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<FontResponse>(
|
||||
items.Select(MapFont),
|
||||
new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<FontResponse> CreateFontAsync(CreateFontRequest req)
|
||||
{
|
||||
var font = new Font
|
||||
{
|
||||
Name = req.Name, OriginalName = req.OriginalName, SystemName = req.SystemName,
|
||||
Family = req.Family, Weight = req.Weight, Style = req.Style, Direction = req.Direction,
|
||||
FileUrl = req.FileUrl, SampleImageUrl = req.SampleImageUrl, IsPremium = req.IsPremium,
|
||||
IsActive = req.IsActive, InstalledOnNodes = req.InstalledOnNodes, Sort = req.Sort
|
||||
};
|
||||
db.Fonts.Add(font);
|
||||
await db.SaveChangesAsync();
|
||||
return MapFont(font);
|
||||
}
|
||||
|
||||
public async Task<FontResponse> UpdateFontAsync(Guid id, UpdateFontRequest req)
|
||||
{
|
||||
var font = await db.Fonts.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Font {id} not found");
|
||||
font.Name = req.Name; font.OriginalName = req.OriginalName; font.SystemName = req.SystemName;
|
||||
font.Family = req.Family; font.Weight = req.Weight; font.Style = req.Style; font.Direction = req.Direction;
|
||||
font.FileUrl = req.FileUrl; font.SampleImageUrl = req.SampleImageUrl; font.IsPremium = req.IsPremium;
|
||||
font.IsActive = req.IsActive; font.InstalledOnNodes = req.InstalledOnNodes; font.Sort = req.Sort;
|
||||
font.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return MapFont(font);
|
||||
}
|
||||
|
||||
public async Task DeleteFontAsync(Guid id)
|
||||
{
|
||||
var font = await db.Fonts.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Font {id} not found");
|
||||
font.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<MusicTrackResponse>> GetMusicTracksAsync(int page, int pageSize, string? search)
|
||||
{
|
||||
var q = db.MusicTracks.AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%"));
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.OrderBy(x => x.Sort).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<MusicTrackResponse>(
|
||||
items.Select(MapMusic),
|
||||
new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<MusicTrackResponse> CreateMusicTrackAsync(CreateMusicTrackRequest req)
|
||||
{
|
||||
var track = new MusicTrack
|
||||
{
|
||||
Name = req.Name, Caption = req.Caption, Keywords = req.Keywords, Url = req.Url,
|
||||
WaveformData = req.WaveformData, DurationSec = req.DurationSec, Bpm = req.Bpm,
|
||||
Genre = req.Genre, Mood = req.Mood, IsPremium = req.IsPremium, IsActive = req.IsActive, Sort = req.Sort
|
||||
};
|
||||
db.MusicTracks.Add(track);
|
||||
await db.SaveChangesAsync();
|
||||
return MapMusic(track);
|
||||
}
|
||||
|
||||
public async Task DeleteMusicTrackAsync(Guid id)
|
||||
{
|
||||
var track = await db.MusicTracks.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"MusicTrack {id} not found");
|
||||
track.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static CategoryResponse MapCategory(Category c) => new(
|
||||
c.Id, c.ParentId, c.Name, c.Slug, c.Description, c.ImageUrl, c.Icon,
|
||||
c.IsActive, c.Sort, c.Children.Select(MapCategory).ToList()
|
||||
);
|
||||
|
||||
private static FontResponse MapFont(Font f) => new(
|
||||
f.Id, f.Name, f.OriginalName, f.SystemName, f.Family, f.Weight, f.Style,
|
||||
f.Direction, f.FileUrl, f.SampleImageUrl, f.IsPremium, f.IsActive, f.InstalledOnNodes
|
||||
);
|
||||
|
||||
private static MusicTrackResponse MapMusic(MusicTrack m) => new(
|
||||
m.Id, m.Name, m.Caption, m.Url, m.WaveformData, m.DurationSec, m.Bpm, m.Genre, m.Mood, m.IsPremium
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using FlatRender.ContentSvc.Domain.Entities;
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
using FlatRender.ContentSvc.Infrastructure.Data;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using FlatRender.ContentSvc.Models.Responses;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FlatRender.ContentSvc.Application.Services;
|
||||
|
||||
public class TemplateService(ContentDbContext db)
|
||||
{
|
||||
public async Task<PagedResponse<ContainerSummaryResponse>> GetContainersAsync(ContainerListRequest req)
|
||||
{
|
||||
var q = db.ProjectContainers
|
||||
.Include(x => x.ContainerCategories).ThenInclude(x => x.Category)
|
||||
.Include(x => x.ContainerTags).ThenInclude(x => x.Tag)
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Search))
|
||||
q = q.Where(x => EF.Functions.ILike(x.Name, $"%{req.Search}%") ||
|
||||
EF.Functions.ILike(x.Slug, $"%{req.Search}%"));
|
||||
|
||||
if (req.CategoryId.HasValue)
|
||||
q = q.Where(x => x.ContainerCategories.Any(cc => cc.CategoryId == req.CategoryId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.TagSlug))
|
||||
q = q.Where(x => x.ContainerTags.Any(ct => ct.Tag.Slug == req.TagSlug));
|
||||
|
||||
if (req.IsPublished.HasValue)
|
||||
q = q.Where(x => x.IsPublished == req.IsPublished);
|
||||
|
||||
if (req.IsPremium.HasValue)
|
||||
q = q.Where(x => x.IsPremium == req.IsPremium);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Mode))
|
||||
{
|
||||
if (Enum.TryParse<ChooseMode>(req.Mode, true, out var mode))
|
||||
q = q.Where(x => x.PrimaryMode == mode);
|
||||
}
|
||||
|
||||
q = req.Sort switch
|
||||
{
|
||||
"sort_asc" => q.OrderBy(x => x.Sort),
|
||||
"name_asc" => q.OrderBy(x => x.Name),
|
||||
"view_count_desc" => q.OrderByDescending(x => x.ViewCount),
|
||||
_ => q.OrderByDescending(x => x.SortDate)
|
||||
};
|
||||
|
||||
var total = await q.LongCountAsync();
|
||||
var items = await q.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToListAsync();
|
||||
|
||||
return new PagedResponse<ContainerSummaryResponse>(
|
||||
items.Select(MapContainerSummary),
|
||||
new PaginationMeta(req.Page, req.PageSize, total, (int)Math.Ceiling((double)total / req.PageSize))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<ContainerDetailResponse> GetContainerBySlugAsync(string slug)
|
||||
{
|
||||
var container = await db.ProjectContainers
|
||||
.Include(x => x.ContainerCategories).ThenInclude(x => x.Category)
|
||||
.Include(x => x.ContainerTags).ThenInclude(x => x.Tag)
|
||||
.Include(x => x.Projects.Where(p => p.DeletedAt == null))
|
||||
.FirstOrDefaultAsync(x => x.Slug == slug)
|
||||
?? throw new KeyNotFoundException($"Container '{slug}' not found");
|
||||
|
||||
return MapContainerDetail(container);
|
||||
}
|
||||
|
||||
public async Task<ContainerDetailResponse> GetContainerByIdAsync(Guid id)
|
||||
{
|
||||
var container = await db.ProjectContainers
|
||||
.Include(x => x.ContainerCategories).ThenInclude(x => x.Category)
|
||||
.Include(x => x.ContainerTags).ThenInclude(x => x.Tag)
|
||||
.Include(x => x.Projects.Where(p => p.DeletedAt == null))
|
||||
.FirstOrDefaultAsync(x => x.Id == id)
|
||||
?? throw new KeyNotFoundException($"Container {id} not found");
|
||||
|
||||
return MapContainerDetail(container);
|
||||
}
|
||||
|
||||
public async Task<ContainerDetailResponse> CreateContainerAsync(CreateContainerRequest req)
|
||||
{
|
||||
if (!Enum.TryParse<ChooseMode>(req.PrimaryMode, true, out var mode))
|
||||
throw new ArgumentException($"Invalid PrimaryMode: {req.PrimaryMode}");
|
||||
|
||||
var container = new ProjectContainer
|
||||
{
|
||||
Slug = req.Slug, Name = req.Name, Description = req.Description, Keywords = req.Keywords,
|
||||
NewsText = req.NewsText, Image = req.Image, Demo = req.Demo, FullDemo = req.FullDemo,
|
||||
MiniDemo = req.MiniDemo, DemoScriptTag = req.DemoScriptTag, IsPublished = req.IsPublished,
|
||||
IsPremium = req.IsPremium, IsMockup = req.IsMockup, PrimaryMode = mode, Sort = req.Sort,
|
||||
SortDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
db.ProjectContainers.Add(container);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await SyncContainerCategoriesAsync(container.Id, req.CategoryIds);
|
||||
await SyncContainerTagsAsync(container.Id, req.TagIds);
|
||||
|
||||
return await GetContainerByIdAsync(container.Id);
|
||||
}
|
||||
|
||||
public async Task<ContainerDetailResponse> UpdateContainerAsync(Guid id, UpdateContainerRequest req)
|
||||
{
|
||||
var container = await db.ProjectContainers.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Container {id} not found");
|
||||
|
||||
if (!Enum.TryParse<ChooseMode>(req.PrimaryMode, true, out var mode))
|
||||
throw new ArgumentException($"Invalid PrimaryMode: {req.PrimaryMode}");
|
||||
|
||||
container.Slug = req.Slug; container.Name = req.Name; container.Description = req.Description;
|
||||
container.Keywords = req.Keywords; container.NewsText = req.NewsText; container.Image = req.Image;
|
||||
container.Demo = req.Demo; container.FullDemo = req.FullDemo; container.MiniDemo = req.MiniDemo;
|
||||
container.DemoScriptTag = req.DemoScriptTag; container.IsPublished = req.IsPublished;
|
||||
container.IsPremium = req.IsPremium; container.IsMockup = req.IsMockup;
|
||||
container.PrimaryMode = mode; container.Sort = req.Sort; container.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
await SyncContainerCategoriesAsync(id, req.CategoryIds);
|
||||
await SyncContainerTagsAsync(id, req.TagIds);
|
||||
|
||||
return await GetContainerByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task DeleteContainerAsync(Guid id)
|
||||
{
|
||||
var container = await db.ProjectContainers.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Container {id} not found");
|
||||
container.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<ProjectDetailResponse> GetProjectDetailAsync(Guid id)
|
||||
{
|
||||
var project = await db.Projects
|
||||
.Include(x => x.Scenes.Where(s => s.DeletedAt == null)).ThenInclude(x => x.RepeaterItems)
|
||||
.Include(x => x.Scenes).ThenInclude(x => x.ContentElements)
|
||||
.Include(x => x.Scenes).ThenInclude(x => x.ColorElements)
|
||||
.Include(x => x.Scenes).ThenInclude(x => x.ColorPresets).ThenInclude(x => x.Items)
|
||||
.Include(x => x.Scenes).ThenInclude(x => x.Characters).ThenInclude(x => x.Controllers).ThenInclude(x => x.Options)
|
||||
.Include(x => x.SharedColors)
|
||||
.Include(x => x.SharedLayers)
|
||||
.FirstOrDefaultAsync(x => x.Id == id)
|
||||
?? throw new KeyNotFoundException($"Project {id} not found");
|
||||
|
||||
return MapProjectDetail(project);
|
||||
}
|
||||
|
||||
public async Task<ProjectDetailResponse> CreateProjectAsync(CreateProjectRequest req)
|
||||
{
|
||||
if (!Enum.TryParse<ChooseMode>(req.ChooseMode, true, out var chooseMode))
|
||||
throw new ArgumentException($"Invalid ChooseMode: {req.ChooseMode}");
|
||||
if (!Enum.TryParse<ResolutionKind>(req.Resolution, true, out var resolution))
|
||||
throw new ArgumentException($"Invalid Resolution: {req.Resolution}");
|
||||
|
||||
var project = new Project
|
||||
{
|
||||
ContainerId = req.ContainerId, ProjectServerId = req.ProjectServerId, Name = req.Name,
|
||||
Description = req.Description, Image = req.Image, FullDemo = req.FullDemo,
|
||||
DemoScriptTag = req.DemoScriptTag, DownloadLink = req.DownloadLink, Folder = req.Folder,
|
||||
OriginalWidth = req.OriginalWidth, OriginalHeight = req.OriginalHeight, Aspect = req.Aspect,
|
||||
ProjectDurationSec = req.ProjectDurationSec, MinDurationSec = req.MinDurationSec,
|
||||
MaxDurationSec = req.MaxDurationSec, FreeFps = req.FreeFps, ChooseMode = chooseMode,
|
||||
Resolution = resolution, VipFactor = req.VipFactor, RenderAepComp = req.RenderAepComp,
|
||||
IsPublished = req.IsPublished, Sort = req.Sort
|
||||
};
|
||||
|
||||
db.Projects.Add(project);
|
||||
await db.SaveChangesAsync();
|
||||
return await GetProjectDetailAsync(project.Id);
|
||||
}
|
||||
|
||||
public async Task<ProjectDetailResponse> UpdateProjectAsync(Guid id, UpdateProjectRequest req)
|
||||
{
|
||||
var project = await db.Projects.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Project {id} not found");
|
||||
|
||||
if (!Enum.TryParse<ChooseMode>(req.ChooseMode, true, out var chooseMode))
|
||||
throw new ArgumentException($"Invalid ChooseMode: {req.ChooseMode}");
|
||||
if (!Enum.TryParse<ResolutionKind>(req.Resolution, true, out var resolution))
|
||||
throw new ArgumentException($"Invalid Resolution: {req.Resolution}");
|
||||
|
||||
project.Name = req.Name; project.Description = req.Description; project.Image = req.Image;
|
||||
project.FullDemo = req.FullDemo; project.DemoScriptTag = req.DemoScriptTag;
|
||||
project.DownloadLink = req.DownloadLink; project.Folder = req.Folder;
|
||||
project.OriginalWidth = req.OriginalWidth; project.OriginalHeight = req.OriginalHeight;
|
||||
project.Aspect = req.Aspect; project.ProjectDurationSec = req.ProjectDurationSec;
|
||||
project.MinDurationSec = req.MinDurationSec; project.MaxDurationSec = req.MaxDurationSec;
|
||||
project.FreeFps = req.FreeFps; project.ChooseMode = chooseMode; project.Resolution = resolution;
|
||||
project.VipFactor = req.VipFactor; project.RenderAepComp = req.RenderAepComp;
|
||||
project.SharedLayerImage = req.SharedLayerImage; project.SharedColorsSvg = req.SharedColorsSvg;
|
||||
project.SharedColorPresetsSvg = req.SharedColorPresetsSvg;
|
||||
project.IsPublished = req.IsPublished; project.Sort = req.Sort;
|
||||
project.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return await GetProjectDetailAsync(project.Id);
|
||||
}
|
||||
|
||||
public async Task DeleteProjectAsync(Guid id)
|
||||
{
|
||||
var project = await db.Projects.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Project {id} not found");
|
||||
project.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task IncrementContainerViewAsync(Guid id)
|
||||
{
|
||||
await db.ProjectContainers
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.ViewCount, x => x.ViewCount + 1));
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task SyncContainerCategoriesAsync(Guid containerId, List<Guid> categoryIds)
|
||||
{
|
||||
var existing = await db.ContainerCategories.Where(x => x.ContainerId == containerId).ToListAsync();
|
||||
db.ContainerCategories.RemoveRange(existing);
|
||||
for (int i = 0; i < categoryIds.Count; i++)
|
||||
db.ContainerCategories.Add(new ContainerCategory { ContainerId = containerId, CategoryId = categoryIds[i], Sort = i });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task SyncContainerTagsAsync(Guid containerId, List<Guid> tagIds)
|
||||
{
|
||||
var existing = await db.ContainerTags.Where(x => x.ContainerId == containerId).ToListAsync();
|
||||
db.ContainerTags.RemoveRange(existing);
|
||||
foreach (var tagId in tagIds)
|
||||
db.ContainerTags.Add(new ContainerTag { ContainerId = containerId, TagId = tagId });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static ContainerSummaryResponse MapContainerSummary(ProjectContainer c) => new(
|
||||
c.Id, c.Slug, c.Name, c.Description, c.Image, c.Demo, c.MiniDemo,
|
||||
c.IsPublished, c.IsPremium, c.IsMockup, c.PrimaryMode.ToString(),
|
||||
c.RateAvg, c.RateCount, c.ViewCount, c.UseCount, c.Sort, c.SortDate,
|
||||
c.ContainerCategories.Select(cc => cc.Category.Slug).ToList(),
|
||||
c.ContainerTags.Select(ct => ct.Tag.Name).ToList()
|
||||
);
|
||||
|
||||
private static ContainerDetailResponse MapContainerDetail(ProjectContainer c) => new(
|
||||
c.Id, c.Slug, c.Name, c.Description, c.Keywords, c.NewsText,
|
||||
c.Image, c.Demo, c.FullDemo, c.MiniDemo, c.DemoScriptTag,
|
||||
c.IsPublished, c.IsPremium, c.IsMockup, c.PrimaryMode.ToString(),
|
||||
c.RateAvg, c.RateCount, c.ViewCount, c.UseCount, c.Sort, c.SortDate,
|
||||
c.Projects.Select(MapProject).ToList(),
|
||||
c.ContainerCategories.Select(cc => MapCategoryFlat(cc.Category)).ToList(),
|
||||
c.ContainerTags.Select(ct => new TagResponse(ct.Tag.Id, ct.Tag.Name, ct.Tag.LatinName, ct.Tag.Slug, ct.Tag.AppliesToMode, ct.Tag.IsActive)).ToList()
|
||||
);
|
||||
|
||||
private static CategoryResponse MapCategoryFlat(Category c) => new(
|
||||
c.Id, c.ParentId, c.Name, c.Slug, c.Description, c.ImageUrl, c.Icon,
|
||||
c.IsActive, c.Sort, []
|
||||
);
|
||||
|
||||
private static ProjectResponse MapProject(Project p) => new(
|
||||
p.Id, p.ContainerId, p.Name, p.Image, p.FullDemo,
|
||||
p.OriginalWidth, p.OriginalHeight, p.Aspect,
|
||||
p.ProjectDurationSec, p.MinDurationSec, p.MaxDurationSec,
|
||||
p.FreeFps, p.ChooseMode.ToString(), p.Resolution.ToString(),
|
||||
p.IsPublished, p.Sort
|
||||
);
|
||||
|
||||
private static ProjectDetailResponse MapProjectDetail(Project p) => new(
|
||||
p.Id, p.ContainerId, p.Name, p.Description, p.Image, p.FullDemo, p.DemoScriptTag, p.DownloadLink,
|
||||
p.OriginalWidth, p.OriginalHeight, p.Aspect,
|
||||
p.ProjectDurationSec, p.MinDurationSec, p.MaxDurationSec,
|
||||
p.FreeFps, p.ChooseMode.ToString(), p.Resolution.ToString(), p.VipFactor, p.RenderAepComp,
|
||||
p.SharedLayerImage, p.IsPublished, p.Sort,
|
||||
p.Scenes.Select(MapScene).ToList(),
|
||||
p.SharedColors.Select(sc => new SharedColorResponse(sc.Id, sc.ElementKey, sc.Title, sc.Icon, sc.AttrValue.ToString(), sc.DefaultColor, sc.Sort)).ToList(),
|
||||
p.SharedLayers.Select(MapSharedLayer).ToList()
|
||||
);
|
||||
|
||||
private static SceneResponse MapScene(Scene s) => new(
|
||||
s.Id, s.ProjectId, s.Key, s.Title, s.LocalizedTitle, s.SceneType.ToString(),
|
||||
s.Image, s.Demo, s.SnapshotUrl, s.GenerateKf,
|
||||
s.DefaultDurationSec, s.MinDurationSec, s.MaxDurationSec, s.OverlapAtEndSec,
|
||||
s.CanHandleDuration, s.ManualColorSelection, s.Sort, s.IsActive
|
||||
);
|
||||
|
||||
private static SharedLayerResponse MapSharedLayer(SharedLayer sl) => new(
|
||||
sl.Id, sl.Key, sl.Title, sl.LocalizedTitle, sl.Hint, sl.Type.ToString(), sl.DefaultValue,
|
||||
sl.FontId, sl.FontFace, sl.FontSize, sl.IsFontChangeable, sl.IsFontSizeChangeable,
|
||||
sl.Justify.ToString(), sl.CanJustify, sl.PositionInContainer, sl.IsTextBox, sl.MaxSize,
|
||||
sl.VideoSupport, sl.MinDurationSec, sl.MaxDurationSec, sl.Width, sl.Height,
|
||||
sl.MappedList, sl.AiInputType.ToString(), sl.IsHidden, sl.IsFocused,
|
||||
sl.VirtualCount, sl.Sort
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System.Security.Claims;
|
||||
using FlatRender.ContentSvc.Application.Services;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FlatRender.ContentSvc.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/blogs")]
|
||||
public class BlogsController(CmsService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List([FromQuery] BlogListRequest req) =>
|
||||
Ok(await svc.GetBlogsAsync(req));
|
||||
|
||||
[HttpGet("{slug}")]
|
||||
public async Task<IActionResult> Get(string slug) =>
|
||||
Ok(await svc.GetBlogBySlugAsync(slug));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateBlogRequest req)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier) is { } s ? Guid.Parse(s) : (Guid?)null;
|
||||
return Ok(await svc.CreateBlogAsync(new BlogListRequest(), req, userId));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateBlogRequest req) =>
|
||||
Ok(await svc.UpdateBlogAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
await svc.DeleteBlogAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/comments")]
|
||||
public class CommentsController(CmsService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] Guid? blogId = null, [FromQuery] Guid? containerId = null,
|
||||
[FromQuery] bool? isApproved = null) =>
|
||||
Ok(await svc.GetCommentsAsync(page, pageSize, blogId, containerId, isApproved));
|
||||
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateCommentRequest req)
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
return Ok(await svc.CreateCommentAsync(req, userId));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPatch("{id:guid}/approve")]
|
||||
public async Task<IActionResult> Approve(Guid id, [FromQuery] bool approve = true)
|
||||
{
|
||||
await svc.ApproveCommentAsync(id, approve);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
await svc.DeleteCommentAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/slides")]
|
||||
public class SlidesController(CmsService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get([FromQuery] Guid? tenantId = null) =>
|
||||
Ok(await svc.GetSlidesAsync(tenantId));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateSlideRequest req) =>
|
||||
Ok(await svc.CreateSlideAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id)
|
||||
{
|
||||
await svc.DeleteSlideAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/home-events")]
|
||||
public class HomePageEventsController(CmsService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get([FromQuery] Guid? tenantId = null) =>
|
||||
Ok(await svc.GetHomePageEventsAsync(tenantId));
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/settings")]
|
||||
public class WebsiteSettingsController(CmsService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get([FromQuery] Guid? tenantId = null) =>
|
||||
Ok(await svc.GetSettingsAsync(tenantId, includeSecret: false));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? tenantId = null) =>
|
||||
Ok(await svc.GetSettingsAsync(tenantId, includeSecret: true));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Upsert([FromQuery] Guid? tenantId, [FromBody] UpsertWebsiteSettingRequest req) =>
|
||||
Ok(await svc.UpsertSettingAsync(tenantId, req));
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/favorites")]
|
||||
[Authorize]
|
||||
public class FavoritesController(CmsService svc) : ControllerBase
|
||||
{
|
||||
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
private Guid TenantId => Guid.Parse(User.FindFirstValue("tenant_id") ?? "00000000-0000-0000-0000-000000000001");
|
||||
|
||||
[HttpGet("folders")]
|
||||
public async Task<IActionResult> GetFolders() =>
|
||||
Ok(await svc.GetFavoriteFoldersAsync(UserId));
|
||||
|
||||
[HttpPost("folders")]
|
||||
public async Task<IActionResult> CreateFolder([FromBody] CreateFavoriteFolderRequest req) =>
|
||||
Ok(await svc.CreateFavoriteFolderAsync(UserId, TenantId, req));
|
||||
|
||||
[HttpDelete("folders/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteFolder(Guid id)
|
||||
{
|
||||
await svc.DeleteFavoriteFolderAsync(UserId, id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("containers")]
|
||||
public async Task<IActionResult> AddContainer([FromBody] AddFavoriteContainerRequest req)
|
||||
{
|
||||
await svc.AddFavoriteContainerAsync(UserId, TenantId, req);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("containers/{containerId:guid}")]
|
||||
public async Task<IActionResult> RemoveContainer(Guid containerId)
|
||||
{
|
||||
await svc.RemoveFavoriteContainerAsync(UserId, containerId);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using FlatRender.ContentSvc.Application.Services;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FlatRender.ContentSvc.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("v1")]
|
||||
public class TaxonomyController(TaxonomyService svc) : ControllerBase
|
||||
{
|
||||
// ── Categories ────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("categories")]
|
||||
public async Task<IActionResult> GetCategories() =>
|
||||
Ok(await svc.GetCategoryTreeAsync());
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("categories")]
|
||||
public async Task<IActionResult> CreateCategory([FromBody] CreateCategoryRequest req) =>
|
||||
Ok(await svc.CreateCategoryAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("categories/{id:guid}")]
|
||||
public async Task<IActionResult> UpdateCategory(Guid id, [FromBody] UpdateCategoryRequest req) =>
|
||||
Ok(await svc.UpdateCategoryAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("categories/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteCategory(Guid id)
|
||||
{
|
||||
await svc.DeleteCategoryAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ── Tags ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("tags")]
|
||||
public async Task<IActionResult> GetTags(
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 50,
|
||||
[FromQuery] string? search = null) =>
|
||||
Ok(await svc.GetTagsAsync(page, pageSize, search));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("tags")]
|
||||
public async Task<IActionResult> CreateTag([FromBody] CreateTagRequest req) =>
|
||||
Ok(await svc.CreateTagAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("tags/{id:guid}")]
|
||||
public async Task<IActionResult> UpdateTag(Guid id, [FromBody] UpdateTagRequest req) =>
|
||||
Ok(await svc.UpdateTagAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("tags/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteTag(Guid id)
|
||||
{
|
||||
await svc.DeleteTagAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ── Fonts ─────────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("fonts")]
|
||||
public async Task<IActionResult> GetFonts(
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 50,
|
||||
[FromQuery] string? search = null, [FromQuery] string? direction = null) =>
|
||||
Ok(await svc.GetFontsAsync(page, pageSize, search, direction));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("fonts")]
|
||||
public async Task<IActionResult> CreateFont([FromBody] CreateFontRequest req) =>
|
||||
Ok(await svc.CreateFontAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("fonts/{id:guid}")]
|
||||
public async Task<IActionResult> UpdateFont(Guid id, [FromBody] UpdateFontRequest req) =>
|
||||
Ok(await svc.UpdateFontAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("fonts/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteFont(Guid id)
|
||||
{
|
||||
await svc.DeleteFontAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ── Music Tracks ──────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("music")]
|
||||
public async Task<IActionResult> GetMusicTracks(
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 50,
|
||||
[FromQuery] string? search = null) =>
|
||||
Ok(await svc.GetMusicTracksAsync(page, pageSize, search));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("music")]
|
||||
public async Task<IActionResult> CreateMusicTrack([FromBody] CreateMusicTrackRequest req) =>
|
||||
Ok(await svc.CreateMusicTrackAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("music/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteMusicTrack(Guid id)
|
||||
{
|
||||
await svc.DeleteMusicTrackAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using FlatRender.ContentSvc.Application.Services;
|
||||
using FlatRender.ContentSvc.Models.Requests;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FlatRender.ContentSvc.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/templates")]
|
||||
public class TemplatesController(TemplateService svc) : ControllerBase
|
||||
{
|
||||
// ── Containers ────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListContainers([FromQuery] ContainerListRequest req) =>
|
||||
Ok(await svc.GetContainersAsync(req));
|
||||
|
||||
[HttpGet("{slug}")]
|
||||
public async Task<IActionResult> GetContainer(string slug)
|
||||
{
|
||||
await svc.IncrementContainerViewAsync(
|
||||
(await svc.GetContainerBySlugAsync(slug)).Id);
|
||||
return Ok(await svc.GetContainerBySlugAsync(slug));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateContainer([FromBody] CreateContainerRequest req) =>
|
||||
Ok(await svc.CreateContainerAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> UpdateContainer(Guid id, [FromBody] UpdateContainerRequest req) =>
|
||||
Ok(await svc.UpdateContainerAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> DeleteContainer(Guid id)
|
||||
{
|
||||
await svc.DeleteContainerAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/projects")]
|
||||
public class ProjectsController(TemplateService svc) : ControllerBase
|
||||
{
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetProject(Guid id) =>
|
||||
Ok(await svc.GetProjectDetailAsync(id));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateProject([FromBody] CreateProjectRequest req) =>
|
||||
Ok(await svc.CreateProjectAsync(req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> UpdateProject(Guid id, [FromBody] UpdateProjectRequest req) =>
|
||||
Ok(await svc.UpdateProjectAsync(id, req));
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> DeleteProject(Guid id)
|
||||
{
|
||||
await svc.DeleteProjectAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
namespace FlatRender.ContentSvc.Domain.Entities;
|
||||
|
||||
public class SceneCharacter
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
|
||||
public string Key { get; set; } = default!;
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<SceneCharacterController> Controllers { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SceneCharacterController
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneCharacterId { get; set; }
|
||||
public SceneCharacter Character { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string Key { get; set; } = default!;
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<SceneControllerOption> Options { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SceneControllerOption
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ControllerId { get; set; }
|
||||
public SceneCharacterController Controller { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
public string Value { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
public class ProjectCharacterController
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string Key { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<ProjectCharacterControllerOption> Options { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ProjectCharacterControllerOption
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ControllerId { get; set; }
|
||||
public ProjectCharacterController Controller { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
public string Value { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
public class ProjectCharacterPreset
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public Guid Key { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<PresetCharacterController> PresetControllers { get; set; } = [];
|
||||
}
|
||||
|
||||
public class PresetCharacterController
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid CharacterPresetId { get; set; }
|
||||
public ProjectCharacterPreset Preset { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string Key { get; set; } = default!;
|
||||
public string Value { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class PresetStory
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
public string? Demo { get; set; }
|
||||
public Guid? MusicId { get; set; }
|
||||
public MusicTrack? Music { get; set; }
|
||||
public string? ScenesSpa { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public bool IsPublished { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<PresetScene> Scenes { get; set; } = [];
|
||||
}
|
||||
|
||||
public class PresetScene
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid PresetStoryId { get; set; }
|
||||
public PresetStory Story { get; set; } = default!;
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public decimal? DefaultDurationSec { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
|
||||
namespace FlatRender.ContentSvc.Domain.Entities;
|
||||
|
||||
public class Blog
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
public BlogKind Kind { get; set; } = BlogKind.Blog;
|
||||
|
||||
public string Slug { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? ShortDescription { get; set; }
|
||||
public string Content { get; set; } = default!;
|
||||
|
||||
public string? MetaTitle { get; set; }
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? MetaKeywords { get; set; }
|
||||
public bool IncludeInSiteMap { get; set; } = true;
|
||||
|
||||
public string? Image { get; set; }
|
||||
public string? Cover { get; set; }
|
||||
|
||||
public Guid? AuthorUserId { get; set; }
|
||||
public string? AuthorDisplayName { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; }
|
||||
public DateTime? PublishDate { get; set; }
|
||||
|
||||
public long ViewCount { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<Comment> Comments { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Comment
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public Guid? BlogId { get; set; }
|
||||
public Blog? Blog { get; set; }
|
||||
public Guid? ContainerId { get; set; }
|
||||
public ProjectContainer? Container { get; set; }
|
||||
public Guid? ParentCommentId { get; set; }
|
||||
public Comment? ParentComment { get; set; }
|
||||
|
||||
public string Content { get; set; } = default!;
|
||||
public decimal? Rate { get; set; }
|
||||
|
||||
public bool IsApproved { get; set; }
|
||||
public bool IsPinned { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<Comment> Replies { get; set; } = [];
|
||||
}
|
||||
|
||||
public class HomePageEvent
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Subtitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Badge { get; set; }
|
||||
public string? BadgeClass { get; set; }
|
||||
public string? ButtonText { get; set; }
|
||||
public string? ButtonUrl { get; set; }
|
||||
public string? ButtonClass { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public string? BackgroundColor { get; set; }
|
||||
public string? TextColor { get; set; }
|
||||
public string? Image { get; set; }
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
public int Sort { get; set; }
|
||||
public DateTime? StartsAt { get; set; }
|
||||
public DateTime? EndsAt { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class NewSlide
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string? Keyword { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Image { get; set; }
|
||||
public string? Parameter { get; set; }
|
||||
public SlideType SlideType { get; set; } = SlideType.Hero;
|
||||
public DateTime? ExpireDate { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class InternalRoute
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
public string? Image { get; set; }
|
||||
public string Slug { get; set; } = default!;
|
||||
public int Priority { get; set; } = 5;
|
||||
public DateTime? LastDate { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class CustomRoute
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Target { get; set; } = default!;
|
||||
public string Destination { get; set; } = default!;
|
||||
public int RedirectCode { get; set; } = 301;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class WebsiteSetting
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Key { get; set; } = default!;
|
||||
public string Value { get; set; } = "{}";
|
||||
public string? Description { get; set; }
|
||||
public bool IsSecret { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class LearnArticle
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Title { get; set; } = default!;
|
||||
public string? Body { get; set; }
|
||||
public string? DemoUrl { get; set; }
|
||||
public string? Mode { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class Training
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Title { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
public string? VideoUrl { get; set; }
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public bool IsPublished { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class FavoriteFolder
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid UserId { get; set; }
|
||||
public Guid TenantId { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<FavoriteContainer> Containers { get; set; } = [];
|
||||
}
|
||||
|
||||
public class FavoriteContainer
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid UserId { get; set; }
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid ContainerId { get; set; }
|
||||
public ProjectContainer Container { get; set; } = default!;
|
||||
public Guid? FolderId { get; set; }
|
||||
public FavoriteFolder? Folder { get; set; }
|
||||
|
||||
public string? Note { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
|
||||
namespace FlatRender.ContentSvc.Domain.Entities;
|
||||
|
||||
public class Scene
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public string Key { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? LocalizedTitle { get; set; }
|
||||
|
||||
public SceneKind SceneType { get; set; } = SceneKind.Normal;
|
||||
|
||||
public string? Image { get; set; }
|
||||
public string? Demo { get; set; }
|
||||
public string? SceneColorSvg { get; set; }
|
||||
public string? SnapshotUrl { get; set; }
|
||||
|
||||
public bool GenerateKf { get; set; }
|
||||
|
||||
public decimal? DefaultDurationSec { get; set; }
|
||||
public decimal? MinDurationSec { get; set; }
|
||||
public decimal? MaxDurationSec { get; set; }
|
||||
public decimal OverlapAtEndSec { get; set; }
|
||||
public bool CanHandleDuration { get; set; } = true;
|
||||
|
||||
public bool ManualColorSelection { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<RepeaterItem> RepeaterItems { get; set; } = [];
|
||||
public ICollection<SceneContentElement> ContentElements { get; set; } = [];
|
||||
public ICollection<SceneColorElement> ColorElements { get; set; } = [];
|
||||
public ICollection<SceneColorPreset> ColorPresets { get; set; } = [];
|
||||
public ICollection<SceneCharacter> Characters { get; set; } = [];
|
||||
public ICollection<SceneCategory> SceneCategories { get; set; } = [];
|
||||
}
|
||||
|
||||
public class RepeaterItem
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
|
||||
public string Title { get; set; } = default!;
|
||||
public string RepeatBoxKey { get; set; } = default!;
|
||||
public string RepeatItemKey { get; set; } = default!;
|
||||
|
||||
public int MaxRepeatCount { get; set; } = 10;
|
||||
public bool UserCanChangeSort { get; set; } = true;
|
||||
public RepeatSortStrategy RepeatSortStrategy { get; set; } = RepeatSortStrategy.Manual;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<SceneContentElement> ContentElements { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SceneContentElement
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
public Guid? RepeaterItemId { get; set; }
|
||||
|
||||
public string Key { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? LocalizedTitle { get; set; }
|
||||
public string? Hint { get; set; }
|
||||
|
||||
public ContentElementType Type { get; set; }
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
// Text
|
||||
public Guid? FontId { get; set; }
|
||||
public string? FontFace { get; set; }
|
||||
public string? FontFaceName { get; set; }
|
||||
public int? FontSize { get; set; }
|
||||
public int? DefaultFontSize { get; set; }
|
||||
public string? DefaultFontFace { get; set; }
|
||||
public bool IsFontChangeable { get; set; } = true;
|
||||
public bool IsFontSizeChangeable { get; set; } = true;
|
||||
public JustifyKind Justify { get; set; } = JustifyKind.CENTER_JUSTIFY;
|
||||
public bool CanJustify { get; set; } = true;
|
||||
public int PositionInContainer { get; set; }
|
||||
public bool IsTextBox { get; set; }
|
||||
public int? MaxSize { get; set; }
|
||||
public string? DirectionLayerKey { get; set; }
|
||||
public int DirectionLayerValue { get; set; }
|
||||
|
||||
// Media
|
||||
public bool VideoSupport { get; set; }
|
||||
public decimal? MinDurationSec { get; set; }
|
||||
public decimal? MaxDurationSec { get; set; }
|
||||
public int? Width { get; set; }
|
||||
public int? Height { get; set; }
|
||||
public string? Thumbnail { get; set; }
|
||||
|
||||
// Misc
|
||||
public string? MappedList { get; set; }
|
||||
public string? CounterMode { get; set; }
|
||||
public AiInputType AiInputType { get; set; } = AiInputType.None;
|
||||
public bool IsHidden { get; set; }
|
||||
public bool IsFocused { get; set; }
|
||||
public string? OpacityControllerKey { get; set; }
|
||||
|
||||
// Legacy design pattern variants
|
||||
public string? Dp1Image { get; set; }
|
||||
public string? Dp1Title { get; set; }
|
||||
public string? Dp2Image { get; set; }
|
||||
public string? Dp2Title { get; set; }
|
||||
public string? Dp3Image { get; set; }
|
||||
public string? Dp3Title { get; set; }
|
||||
public string? Dp4Image { get; set; }
|
||||
public string? Dp4Title { get; set; }
|
||||
|
||||
public int VirtualCount { get; set; } = 1;
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class SceneColorElement
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
|
||||
public string ElementKey { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
public AttrValueKind AttrValue { get; set; } = AttrValueKind.fill;
|
||||
public string DefaultColor { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class SceneColorPreset
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid SceneId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<SceneColorPresetItem> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SceneColorPresetItem
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid PresetId { get; set; }
|
||||
public SceneColorPreset Preset { get; set; } = default!;
|
||||
public string ElementKey { get; set; } = default!;
|
||||
public string Value { get; set; } = default!;
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
public class SharedColor
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public string ElementKey { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? Icon { get; set; }
|
||||
public AttrValueKind AttrValue { get; set; } = AttrValueKind.fill;
|
||||
public string DefaultColor { get; set; } = default!;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class SharedLayer
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
|
||||
public string Key { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? LocalizedTitle { get; set; }
|
||||
public string? Hint { get; set; }
|
||||
|
||||
public ContentElementType Type { get; set; }
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
public Guid? FontId { get; set; }
|
||||
public string? FontFace { get; set; }
|
||||
public string? FontFaceName { get; set; }
|
||||
public int? FontSize { get; set; }
|
||||
public int? DefaultFontSize { get; set; }
|
||||
public string? DefaultFontFace { get; set; }
|
||||
public bool IsFontChangeable { get; set; } = true;
|
||||
public bool IsFontSizeChangeable { get; set; } = true;
|
||||
public JustifyKind Justify { get; set; } = JustifyKind.CENTER_JUSTIFY;
|
||||
public bool CanJustify { get; set; } = true;
|
||||
public int PositionInContainer { get; set; }
|
||||
public bool IsTextBox { get; set; }
|
||||
public int? MaxSize { get; set; }
|
||||
public string? DirectionLayerKey { get; set; }
|
||||
public int DirectionLayerValue { get; set; }
|
||||
|
||||
public bool VideoSupport { get; set; }
|
||||
public decimal? MinDurationSec { get; set; }
|
||||
public decimal? MaxDurationSec { get; set; }
|
||||
public int? Width { get; set; }
|
||||
public int? Height { get; set; }
|
||||
public string? Thumbnail { get; set; }
|
||||
|
||||
public string? MappedList { get; set; }
|
||||
public string? CounterMode { get; set; }
|
||||
public AiInputType AiInputType { get; set; } = AiInputType.None;
|
||||
public bool IsHidden { get; set; }
|
||||
public bool IsFocused { get; set; }
|
||||
|
||||
// Legacy design pattern variants
|
||||
public string? Dp1Image { get; set; }
|
||||
public string? Dp1Title { get; set; }
|
||||
public string? Dp2Image { get; set; }
|
||||
public string? Dp2Title { get; set; }
|
||||
public string? Dp3Image { get; set; }
|
||||
public string? Dp3Title { get; set; }
|
||||
public string? Dp4Image { get; set; }
|
||||
public string? Dp4Title { get; set; }
|
||||
|
||||
public int VirtualCount { get; set; } = 1;
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class SceneCategory
|
||||
{
|
||||
public Guid SceneId { get; set; }
|
||||
public Scene Scene { get; set; } = default!;
|
||||
public Guid CategoryId { get; set; }
|
||||
public Category Category { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class SharedColorPreset
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ProjectId { get; set; }
|
||||
public Project Project { get; set; } = default!;
|
||||
public string? Name { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<SharedColorPresetItem> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SharedColorPresetItem
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid PresetId { get; set; }
|
||||
public SharedColorPreset Preset { get; set; } = default!;
|
||||
public string ElementKey { get; set; } = default!;
|
||||
public string Value { get; set; } = default!;
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
public class TemplateSvgPreview
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? ProjectId { get; set; }
|
||||
public Guid? SceneId { get; set; }
|
||||
|
||||
public string? SourceImageUrl { get; set; }
|
||||
public string SvgUrl { get; set; } = default!;
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
|
||||
public string ColorZones { get; set; } = "[]";
|
||||
|
||||
public int? Width { get; set; }
|
||||
public int? Height { get; set; }
|
||||
public string? GenerationMethod { get; set; }
|
||||
public bool GeneratedByAi { get; set; }
|
||||
public decimal? QualityScore { get; set; }
|
||||
|
||||
public Guid? CreatedByUserId { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
namespace FlatRender.ContentSvc.Domain.Entities;
|
||||
|
||||
public class Category
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? ParentId { get; set; }
|
||||
public Category? Parent { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string Slug { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string? MetaTitle { get; set; }
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? MetaKeywords { get; set; }
|
||||
public bool BotFollow { get; set; } = true;
|
||||
|
||||
public int Sort { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<Category> Children { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Tag
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = default!;
|
||||
public string? LatinName { get; set; }
|
||||
public string Slug { get; set; } = default!;
|
||||
public string? AppliesToMode { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
}
|
||||
|
||||
public class Font
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = default!;
|
||||
public string? OriginalName { get; set; }
|
||||
public string? SystemName { get; set; }
|
||||
public string? Family { get; set; }
|
||||
public int? Weight { get; set; }
|
||||
public string? Style { get; set; }
|
||||
public string Direction { get; set; } = "LTR";
|
||||
public string? FileUrl { get; set; }
|
||||
public string? SampleImageUrl { get; set; }
|
||||
public bool IsPremium { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool InstalledOnNodes { get; set; }
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
}
|
||||
|
||||
public class MusicTrack
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Caption { get; set; }
|
||||
public string? Keywords { get; set; }
|
||||
public string Url { get; set; } = default!;
|
||||
public string? WaveformData { get; set; }
|
||||
public decimal DurationSec { get; set; }
|
||||
public int? Bpm { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public string? Mood { get; set; }
|
||||
public bool IsPremium { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public int Sort { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
}
|
||||
|
||||
public class ProjectServer
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string Name { get; set; } = default!;
|
||||
public string Region { get; set; } = default!;
|
||||
public string? Ip { get; set; }
|
||||
public string? PhysicalPathOutput { get; set; }
|
||||
public string? DefaultProjectAddress { get; set; }
|
||||
public string? RenderOutputLocation { get; set; }
|
||||
public string? PreNeedFolderAddress { get; set; }
|
||||
public string? MinioEndpoint { get; set; }
|
||||
public string? MinioBucketTemplates { get; set; }
|
||||
public string? MinioBucketOutputs { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class AdminFile
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string? Name { get; set; }
|
||||
public string Url { get; set; } = default!;
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
public string? FileType { get; set; }
|
||||
public long? SizeBytes { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
|
||||
namespace FlatRender.ContentSvc.Domain.Entities;
|
||||
|
||||
public class ProjectContainer
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Slug { get; set; } = default!;
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
public string? Keywords { get; set; }
|
||||
public string? NewsText { get; set; }
|
||||
|
||||
public string? Image { get; set; }
|
||||
public string? Demo { get; set; }
|
||||
public string? FullDemo { get; set; }
|
||||
public string? MiniDemo { get; set; }
|
||||
public string? DemoScriptTag { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; }
|
||||
public bool IsPremium { get; set; }
|
||||
public bool IsMockup { get; set; }
|
||||
public ChooseMode PrimaryMode { get; set; } = ChooseMode.FLEXIBLE;
|
||||
|
||||
public decimal? RateAvg { get; set; }
|
||||
public int RateCount { get; set; }
|
||||
public long ViewCount { get; set; }
|
||||
public long UseCount { get; set; }
|
||||
|
||||
public int Sort { get; set; }
|
||||
public DateTime SortDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<Project> Projects { get; set; } = [];
|
||||
public ICollection<ContainerCategory> ContainerCategories { get; set; } = [];
|
||||
public ICollection<ContainerTag> ContainerTags { get; set; } = [];
|
||||
public ICollection<Comment> Comments { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ContainerCategory
|
||||
{
|
||||
public Guid ContainerId { get; set; }
|
||||
public ProjectContainer Container { get; set; } = default!;
|
||||
public Guid CategoryId { get; set; }
|
||||
public Category Category { get; set; } = default!;
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
public class ContainerTag
|
||||
{
|
||||
public Guid ContainerId { get; set; }
|
||||
public ProjectContainer Container { get; set; } = default!;
|
||||
public Guid TagId { get; set; }
|
||||
public Tag Tag { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class Project
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid ContainerId { get; set; }
|
||||
public ProjectContainer Container { get; set; } = default!;
|
||||
public Guid? ProjectServerId { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
public string? Description { get; set; }
|
||||
public string? Image { get; set; }
|
||||
public string? FullDemo { get; set; }
|
||||
public string? DemoScriptTag { get; set; }
|
||||
public string? DownloadLink { get; set; }
|
||||
|
||||
public string? AepMinioBucket { get; set; }
|
||||
public string? AepMinioKey { get; set; }
|
||||
public string? AepFileUrl { get; set; }
|
||||
public string? AepFileMd5 { get; set; }
|
||||
public long? AepFileSizeBytes { get; set; }
|
||||
public DateTime? AepUploadedAt { get; set; }
|
||||
public string? Folder { get; set; }
|
||||
|
||||
public int OriginalWidth { get; set; }
|
||||
public int OriginalHeight { get; set; }
|
||||
public string? Aspect { get; set; }
|
||||
|
||||
public decimal ProjectDurationSec { get; set; }
|
||||
public decimal? MinDurationSec { get; set; }
|
||||
public decimal? MaxDurationSec { get; set; }
|
||||
public int FreeFps { get; set; } = 30;
|
||||
|
||||
public ChooseMode ChooseMode { get; set; }
|
||||
public ResolutionKind Resolution { get; set; } = ResolutionKind.FullHD;
|
||||
public decimal VipFactor { get; set; } = 1.0m;
|
||||
public string RenderAepComp { get; set; } = "flatrender";
|
||||
|
||||
public string? SharedLayerImage { get; set; }
|
||||
public string? SharedColorsSvg { get; set; }
|
||||
public string? SharedColorPresetsSvg { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; }
|
||||
public int Sort { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
public ICollection<Scene> Scenes { get; set; } = [];
|
||||
public ICollection<SharedColor> SharedColors { get; set; } = [];
|
||||
public ICollection<SharedColorPreset> SharedColorPresets { get; set; } = [];
|
||||
public ICollection<SharedLayer> SharedLayers { get; set; } = [];
|
||||
public ICollection<ProjectCharacterController> CharacterControllers { get; set; } = [];
|
||||
public ICollection<ProjectCharacterPreset> CharacterPresets { get; set; } = [];
|
||||
public ICollection<PresetStory> PresetStories { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FlatRender.ContentSvc.Domain.Enums;
|
||||
|
||||
public enum ChooseMode { FIX, FLEXIBLE, MockUp, MusicVisualizer, VoiceOver }
|
||||
public enum ResolutionKind { HD, FullHD, TwoK, FourK }
|
||||
public enum SceneKind { Normal, Config, DesignStart, DesignEnd }
|
||||
public enum ContentElementType
|
||||
{
|
||||
Text, TextArea, Media, Audio, Voiceover,
|
||||
CheckBox, DropDown, Fill, Color, Number,
|
||||
Date, Toggle, Slider, Counter, Hidden
|
||||
}
|
||||
public enum JustifyKind { LEFT_JUSTIFY, CENTER_JUSTIFY, RIGHT_JUSTIFY, FULL_JUSTIFY }
|
||||
public enum AiInputType { None, TitleSuggest, BodySuggest, TranslateRtl, TranslateLtr, RemoveBG, UpscaleImage, TTS }
|
||||
public enum RepeatSortStrategy { Manual, Alphabetical, Numerical, InsertOrder }
|
||||
public enum AttrValueKind { fill, stroke, tracking, dropshadow }
|
||||
public enum BlogKind { Blog, Landing }
|
||||
public enum SlideType { Hero, Promo, Tutorial, Category, Custom }
|
||||
public enum ContainerFavoriteKind { Container }
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.*" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
@FlatRender.ContentSvc_HostAddress = http://localhost:5088
|
||||
|
||||
GET {{FlatRender.ContentSvc_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,903 @@
|
||||
using FlatRender.ContentSvc.Domain.Entities;
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FlatRender.ContentSvc.Infrastructure.Data;
|
||||
|
||||
public class ContentDbContext(DbContextOptions<ContentDbContext> options) : DbContext(options)
|
||||
{
|
||||
// Taxonomy
|
||||
public DbSet<Category> Categories => Set<Category>();
|
||||
public DbSet<Tag> Tags => Set<Tag>();
|
||||
public DbSet<Font> Fonts => Set<Font>();
|
||||
public DbSet<MusicTrack> MusicTracks => Set<MusicTrack>();
|
||||
public DbSet<ProjectServer> ProjectServers => Set<ProjectServer>();
|
||||
public DbSet<AdminFile> AdminFiles => Set<AdminFile>();
|
||||
|
||||
// Templates
|
||||
public DbSet<ProjectContainer> ProjectContainers => Set<ProjectContainer>();
|
||||
public DbSet<ContainerCategory> ContainerCategories => Set<ContainerCategory>();
|
||||
public DbSet<ContainerTag> ContainerTags => Set<ContainerTag>();
|
||||
public DbSet<Project> Projects => Set<Project>();
|
||||
|
||||
// Scenes
|
||||
public DbSet<Scene> Scenes => Set<Scene>();
|
||||
public DbSet<SceneCategory> SceneCategories => Set<SceneCategory>();
|
||||
public DbSet<RepeaterItem> RepeaterItems => Set<RepeaterItem>();
|
||||
public DbSet<SceneContentElement> SceneContentElements => Set<SceneContentElement>();
|
||||
public DbSet<SceneColorElement> SceneColorElements => Set<SceneColorElement>();
|
||||
public DbSet<SceneColorPreset> SceneColorPresets => Set<SceneColorPreset>();
|
||||
public DbSet<SceneColorPresetItem> SceneColorPresetItems => Set<SceneColorPresetItem>();
|
||||
public DbSet<SharedColor> SharedColors => Set<SharedColor>();
|
||||
public DbSet<SharedColorPreset> SharedColorPresets => Set<SharedColorPreset>();
|
||||
public DbSet<SharedColorPresetItem> SharedColorPresetItems => Set<SharedColorPresetItem>();
|
||||
public DbSet<SharedLayer> SharedLayers => Set<SharedLayer>();
|
||||
public DbSet<TemplateSvgPreview> TemplateSvgPreviews => Set<TemplateSvgPreview>();
|
||||
|
||||
// Characters
|
||||
public DbSet<SceneCharacter> SceneCharacters => Set<SceneCharacter>();
|
||||
public DbSet<SceneCharacterController> SceneCharacterControllers => Set<SceneCharacterController>();
|
||||
public DbSet<SceneControllerOption> SceneControllerOptions => Set<SceneControllerOption>();
|
||||
public DbSet<ProjectCharacterController> ProjectCharacterControllers => Set<ProjectCharacterController>();
|
||||
public DbSet<ProjectCharacterControllerOption> ProjectCharacterControllerOptions => Set<ProjectCharacterControllerOption>();
|
||||
public DbSet<ProjectCharacterPreset> ProjectCharacterPresets => Set<ProjectCharacterPreset>();
|
||||
public DbSet<PresetCharacterController> PresetCharacterControllers => Set<PresetCharacterController>();
|
||||
public DbSet<PresetStory> PresetStories => Set<PresetStory>();
|
||||
public DbSet<PresetScene> PresetScenes => Set<PresetScene>();
|
||||
|
||||
// CMS
|
||||
public DbSet<Blog> Blogs => Set<Blog>();
|
||||
public DbSet<Comment> Comments => Set<Comment>();
|
||||
public DbSet<HomePageEvent> HomePageEvents => Set<HomePageEvent>();
|
||||
public DbSet<NewSlide> NewSlides => Set<NewSlide>();
|
||||
public DbSet<InternalRoute> InternalRoutes => Set<InternalRoute>();
|
||||
public DbSet<CustomRoute> CustomRoutes => Set<CustomRoute>();
|
||||
public DbSet<WebsiteSetting> WebsiteSettings => Set<WebsiteSetting>();
|
||||
public DbSet<LearnArticle> LearnArticles => Set<LearnArticle>();
|
||||
public DbSet<Training> Trainings => Set<Training>();
|
||||
public DbSet<FavoriteFolder> FavoriteFolders => Set<FavoriteFolder>();
|
||||
public DbSet<FavoriteContainer> FavoriteContainers => Set<FavoriteContainer>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder mb)
|
||||
{
|
||||
mb.HasDefaultSchema("content");
|
||||
|
||||
// Native PostgreSQL enums are registered on the EF provider via npgsql.MapEnum<T>()
|
||||
// in Program.cs (EF Core 9+ approach), covering both model + runtime ADO mapping.
|
||||
|
||||
ConfigureTaxonomy(mb);
|
||||
ConfigureTemplates(mb);
|
||||
ConfigureScenes(mb);
|
||||
ConfigureCharacters(mb);
|
||||
ConfigureCms(mb);
|
||||
}
|
||||
|
||||
private static void ConfigureTaxonomy(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<Category>(e =>
|
||||
{
|
||||
e.ToTable("categories");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ParentId).HasColumnName("parent_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.ImageUrl).HasColumnName("image_url");
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.MetaTitle).HasColumnName("meta_title");
|
||||
e.Property(x => x.MetaDescription).HasColumnName("meta_description");
|
||||
e.Property(x => x.MetaKeywords).HasColumnName("meta_keywords");
|
||||
e.Property(x => x.BotFollow).HasColumnName("bot_follow");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasOne(x => x.Parent).WithMany(x => x.Children).HasForeignKey(x => x.ParentId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<Tag>(e =>
|
||||
{
|
||||
e.ToTable("tags");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.LatinName).HasColumnName("latin_name");
|
||||
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
|
||||
e.Property(x => x.AppliesToMode).HasColumnName("applies_to_mode");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<Font>(e =>
|
||||
{
|
||||
e.ToTable("fonts");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.OriginalName).HasColumnName("original_name");
|
||||
e.Property(x => x.SystemName).HasColumnName("system_name");
|
||||
e.Property(x => x.Family).HasColumnName("family");
|
||||
e.Property(x => x.Weight).HasColumnName("weight");
|
||||
e.Property(x => x.Style).HasColumnName("style");
|
||||
e.Property(x => x.Direction).HasColumnName("direction");
|
||||
e.Property(x => x.FileUrl).HasColumnName("file_url");
|
||||
e.Property(x => x.SampleImageUrl).HasColumnName("sample_image_url");
|
||||
e.Property(x => x.IsPremium).HasColumnName("is_premium");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.InstalledOnNodes).HasColumnName("installed_on_nodes");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<MusicTrack>(e =>
|
||||
{
|
||||
e.ToTable("music_tracks");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Caption).HasColumnName("caption");
|
||||
e.Property(x => x.Keywords).HasColumnName("keywords");
|
||||
e.Property(x => x.Url).HasColumnName("url").IsRequired();
|
||||
e.Property(x => x.WaveformData).HasColumnName("waveform_data").HasColumnType("jsonb");
|
||||
e.Property(x => x.DurationSec).HasColumnName("duration_sec");
|
||||
e.Property(x => x.Bpm).HasColumnName("bpm");
|
||||
e.Property(x => x.Genre).HasColumnName("genre");
|
||||
e.Property(x => x.Mood).HasColumnName("mood");
|
||||
e.Property(x => x.IsPremium).HasColumnName("is_premium");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<ProjectServer>(e =>
|
||||
{
|
||||
e.ToTable("project_servers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Region).HasColumnName("region").IsRequired();
|
||||
e.Property(x => x.Ip).HasColumnName("ip");
|
||||
e.Property(x => x.PhysicalPathOutput).HasColumnName("physical_path_output");
|
||||
e.Property(x => x.DefaultProjectAddress).HasColumnName("default_project_address");
|
||||
e.Property(x => x.RenderOutputLocation).HasColumnName("render_output_location");
|
||||
e.Property(x => x.PreNeedFolderAddress).HasColumnName("pre_need_folder_address");
|
||||
e.Property(x => x.MinioEndpoint).HasColumnName("minio_endpoint");
|
||||
e.Property(x => x.MinioBucketTemplates).HasColumnName("minio_bucket_templates");
|
||||
e.Property(x => x.MinioBucketOutputs).HasColumnName("minio_bucket_outputs");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<AdminFile>(e =>
|
||||
{
|
||||
e.ToTable("admin_files");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.Name).HasColumnName("name");
|
||||
e.Property(x => x.Url).HasColumnName("url").IsRequired();
|
||||
e.Property(x => x.ThumbnailUrl).HasColumnName("thumbnail_url");
|
||||
e.Property(x => x.FileType).HasColumnName("file_type");
|
||||
e.Property(x => x.SizeBytes).HasColumnName("size_bytes");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureTemplates(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<ProjectContainer>(e =>
|
||||
{
|
||||
e.ToTable("project_containers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.Keywords).HasColumnName("keywords");
|
||||
e.Property(x => x.NewsText).HasColumnName("news_text");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.Demo).HasColumnName("demo");
|
||||
e.Property(x => x.FullDemo).HasColumnName("full_demo");
|
||||
e.Property(x => x.MiniDemo).HasColumnName("mini_demo");
|
||||
e.Property(x => x.DemoScriptTag).HasColumnName("demo_script_tag");
|
||||
e.Property(x => x.IsPublished).HasColumnName("is_published");
|
||||
e.Property(x => x.IsPremium).HasColumnName("is_premium");
|
||||
e.Property(x => x.IsMockup).HasColumnName("is_mockup");
|
||||
e.Property(x => x.PrimaryMode).HasColumnName("primary_mode");
|
||||
e.Property(x => x.RateAvg).HasColumnName("rate_avg");
|
||||
e.Property(x => x.RateCount).HasColumnName("rate_count");
|
||||
e.Property(x => x.ViewCount).HasColumnName("view_count");
|
||||
e.Property(x => x.UseCount).HasColumnName("use_count");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.SortDate).HasColumnName("sort_date");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<ContainerCategory>(e =>
|
||||
{
|
||||
e.ToTable("container_categories");
|
||||
e.HasKey(x => new { x.ContainerId, x.CategoryId });
|
||||
e.Property(x => x.ContainerId).HasColumnName("container_id");
|
||||
e.Property(x => x.CategoryId).HasColumnName("category_id");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.HasOne(x => x.Container).WithMany(x => x.ContainerCategories).HasForeignKey(x => x.ContainerId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Category).WithMany().HasForeignKey(x => x.CategoryId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<ContainerTag>(e =>
|
||||
{
|
||||
e.ToTable("container_tags");
|
||||
e.HasKey(x => new { x.ContainerId, x.TagId });
|
||||
e.Property(x => x.ContainerId).HasColumnName("container_id");
|
||||
e.Property(x => x.TagId).HasColumnName("tag_id");
|
||||
e.HasOne(x => x.Container).WithMany(x => x.ContainerTags).HasForeignKey(x => x.ContainerId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Tag).WithMany().HasForeignKey(x => x.TagId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<Project>(e =>
|
||||
{
|
||||
e.ToTable("projects");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ContainerId).HasColumnName("container_id");
|
||||
e.Property(x => x.ProjectServerId).HasColumnName("project_server_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.FullDemo).HasColumnName("full_demo");
|
||||
e.Property(x => x.DemoScriptTag).HasColumnName("demo_script_tag");
|
||||
e.Property(x => x.DownloadLink).HasColumnName("download_link");
|
||||
e.Property(x => x.AepMinioBucket).HasColumnName("aep_minio_bucket");
|
||||
e.Property(x => x.AepMinioKey).HasColumnName("aep_minio_key");
|
||||
e.Property(x => x.AepFileUrl).HasColumnName("aep_file_url");
|
||||
e.Property(x => x.AepFileMd5).HasColumnName("aep_file_md5");
|
||||
e.Property(x => x.AepFileSizeBytes).HasColumnName("aep_file_size_bytes");
|
||||
e.Property(x => x.AepUploadedAt).HasColumnName("aep_uploaded_at");
|
||||
e.Property(x => x.Folder).HasColumnName("folder");
|
||||
e.Property(x => x.OriginalWidth).HasColumnName("original_width");
|
||||
e.Property(x => x.OriginalHeight).HasColumnName("original_height");
|
||||
e.Property(x => x.Aspect).HasColumnName("aspect");
|
||||
e.Property(x => x.ProjectDurationSec).HasColumnName("project_duration_sec");
|
||||
e.Property(x => x.MinDurationSec).HasColumnName("min_duration_sec");
|
||||
e.Property(x => x.MaxDurationSec).HasColumnName("max_duration_sec");
|
||||
e.Property(x => x.FreeFps).HasColumnName("free_fps");
|
||||
e.Property(x => x.ChooseMode).HasColumnName("choose_mode");
|
||||
e.Property(x => x.Resolution).HasColumnName("resolution");
|
||||
e.Property(x => x.VipFactor).HasColumnName("vip_factor");
|
||||
e.Property(x => x.RenderAepComp).HasColumnName("render_aep_comp");
|
||||
e.Property(x => x.SharedLayerImage).HasColumnName("shared_layer_image");
|
||||
e.Property(x => x.SharedColorsSvg).HasColumnName("shared_colors_svg");
|
||||
e.Property(x => x.SharedColorPresetsSvg).HasColumnName("shared_color_presets_svg");
|
||||
e.Property(x => x.IsPublished).HasColumnName("is_published");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasOne(x => x.Container).WithMany(x => x.Projects).HasForeignKey(x => x.ContainerId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureScenes(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<Scene>(e =>
|
||||
{
|
||||
e.ToTable("scenes");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.LocalizedTitle).HasColumnName("localized_title").HasColumnType("jsonb");
|
||||
e.Property(x => x.SceneType).HasColumnName("scene_type");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.Demo).HasColumnName("demo");
|
||||
e.Property(x => x.SceneColorSvg).HasColumnName("scene_color_svg");
|
||||
e.Property(x => x.SnapshotUrl).HasColumnName("snapshot_url");
|
||||
e.Property(x => x.GenerateKf).HasColumnName("generate_kf");
|
||||
e.Property(x => x.DefaultDurationSec).HasColumnName("default_duration_sec");
|
||||
e.Property(x => x.MinDurationSec).HasColumnName("min_duration_sec");
|
||||
e.Property(x => x.MaxDurationSec).HasColumnName("max_duration_sec");
|
||||
e.Property(x => x.OverlapAtEndSec).HasColumnName("overlap_at_end_sec");
|
||||
e.Property(x => x.CanHandleDuration).HasColumnName("can_handle_duration");
|
||||
e.Property(x => x.ManualColorSelection).HasColumnName("manual_color_selection");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.Scenes).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<SceneCategory>(e =>
|
||||
{
|
||||
e.ToTable("scene_categories");
|
||||
e.HasKey(x => new { x.SceneId, x.CategoryId });
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.CategoryId).HasColumnName("category_id");
|
||||
e.HasOne(x => x.Scene).WithMany(x => x.SceneCategories).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Category).WithMany().HasForeignKey(x => x.CategoryId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<RepeaterItem>(e =>
|
||||
{
|
||||
e.ToTable("repeater_items");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.RepeatBoxKey).HasColumnName("repeat_box_key").IsRequired();
|
||||
e.Property(x => x.RepeatItemKey).HasColumnName("repeat_item_key").IsRequired();
|
||||
e.Property(x => x.MaxRepeatCount).HasColumnName("max_repeat_count");
|
||||
e.Property(x => x.UserCanChangeSort).HasColumnName("user_can_change_sort");
|
||||
e.Property(x => x.RepeatSortStrategy).HasColumnName("repeat_sort_strategy");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Scene).WithMany(x => x.RepeaterItems).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneContentElement>(e =>
|
||||
{
|
||||
e.ToTable("scene_content_elements");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.RepeaterItemId).HasColumnName("repeater_item_id");
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.LocalizedTitle).HasColumnName("localized_title").HasColumnType("jsonb");
|
||||
e.Property(x => x.Hint).HasColumnName("hint");
|
||||
e.Property(x => x.Type).HasColumnName("type");
|
||||
e.Property(x => x.DefaultValue).HasColumnName("default_value");
|
||||
e.Property(x => x.FontId).HasColumnName("font_id");
|
||||
e.Property(x => x.FontFace).HasColumnName("font_face");
|
||||
e.Property(x => x.FontFaceName).HasColumnName("font_face_name");
|
||||
e.Property(x => x.FontSize).HasColumnName("font_size");
|
||||
e.Property(x => x.DefaultFontSize).HasColumnName("default_font_size");
|
||||
e.Property(x => x.DefaultFontFace).HasColumnName("default_font_face");
|
||||
e.Property(x => x.IsFontChangeable).HasColumnName("is_font_changeable");
|
||||
e.Property(x => x.IsFontSizeChangeable).HasColumnName("is_font_size_changeable");
|
||||
e.Property(x => x.Justify).HasColumnName("justify");
|
||||
e.Property(x => x.CanJustify).HasColumnName("can_justify");
|
||||
e.Property(x => x.PositionInContainer).HasColumnName("position_in_container");
|
||||
e.Property(x => x.IsTextBox).HasColumnName("is_text_box");
|
||||
e.Property(x => x.MaxSize).HasColumnName("max_size");
|
||||
e.Property(x => x.DirectionLayerKey).HasColumnName("direction_layer_key");
|
||||
e.Property(x => x.DirectionLayerValue).HasColumnName("direction_layer_value");
|
||||
e.Property(x => x.VideoSupport).HasColumnName("video_support");
|
||||
e.Property(x => x.MinDurationSec).HasColumnName("min_duration_sec");
|
||||
e.Property(x => x.MaxDurationSec).HasColumnName("max_duration_sec");
|
||||
e.Property(x => x.Width).HasColumnName("width");
|
||||
e.Property(x => x.Height).HasColumnName("height");
|
||||
e.Property(x => x.Thumbnail).HasColumnName("thumbnail");
|
||||
e.Property(x => x.MappedList).HasColumnName("mapped_list").HasColumnType("jsonb");
|
||||
e.Property(x => x.CounterMode).HasColumnName("counter_mode");
|
||||
e.Property(x => x.AiInputType).HasColumnName("ai_input_type");
|
||||
e.Property(x => x.IsHidden).HasColumnName("is_hidden");
|
||||
e.Property(x => x.IsFocused).HasColumnName("is_focused");
|
||||
e.Property(x => x.OpacityControllerKey).HasColumnName("opacity_controller_key");
|
||||
e.Property(x => x.Dp1Image).HasColumnName("dp1_image");
|
||||
e.Property(x => x.Dp1Title).HasColumnName("dp1_title");
|
||||
e.Property(x => x.Dp2Image).HasColumnName("dp2_image");
|
||||
e.Property(x => x.Dp2Title).HasColumnName("dp2_title");
|
||||
e.Property(x => x.Dp3Image).HasColumnName("dp3_image");
|
||||
e.Property(x => x.Dp3Title).HasColumnName("dp3_title");
|
||||
e.Property(x => x.Dp4Image).HasColumnName("dp4_image");
|
||||
e.Property(x => x.Dp4Title).HasColumnName("dp4_title");
|
||||
e.Property(x => x.VirtualCount).HasColumnName("virtual_count");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Scene).WithMany(x => x.ContentElements).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneColorElement>(e =>
|
||||
{
|
||||
e.ToTable("scene_color_elements");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.AttrValue).HasColumnName("attr_value");
|
||||
e.Property(x => x.DefaultColor).HasColumnName("default_color").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Scene).WithMany(x => x.ColorElements).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneColorPreset>(e =>
|
||||
{
|
||||
e.ToTable("scene_color_presets");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.Name).HasColumnName("name");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.HasOne<Scene>().WithMany(x => x.ColorPresets).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneColorPresetItem>(e =>
|
||||
{
|
||||
e.ToTable("scene_color_preset_items");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.PresetId).HasColumnName("preset_id");
|
||||
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
||||
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.HasOne(x => x.Preset).WithMany(x => x.Items).HasForeignKey(x => x.PresetId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SharedColor>(e =>
|
||||
{
|
||||
e.ToTable("shared_colors");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.AttrValue).HasColumnName("attr_value");
|
||||
e.Property(x => x.DefaultColor).HasColumnName("default_color").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.SharedColors).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SharedColorPreset>(e =>
|
||||
{
|
||||
e.ToTable("shared_color_presets");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Name).HasColumnName("name");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.SharedColorPresets).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SharedColorPresetItem>(e =>
|
||||
{
|
||||
e.ToTable("shared_color_preset_items");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.PresetId).HasColumnName("preset_id");
|
||||
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
||||
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.HasOne(x => x.Preset).WithMany(x => x.Items).HasForeignKey(x => x.PresetId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SharedLayer>(e =>
|
||||
{
|
||||
e.ToTable("shared_layers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.LocalizedTitle).HasColumnName("localized_title").HasColumnType("jsonb");
|
||||
e.Property(x => x.Hint).HasColumnName("hint");
|
||||
e.Property(x => x.Type).HasColumnName("type");
|
||||
e.Property(x => x.DefaultValue).HasColumnName("default_value");
|
||||
e.Property(x => x.FontId).HasColumnName("font_id");
|
||||
e.Property(x => x.FontFace).HasColumnName("font_face");
|
||||
e.Property(x => x.FontFaceName).HasColumnName("font_face_name");
|
||||
e.Property(x => x.FontSize).HasColumnName("font_size");
|
||||
e.Property(x => x.DefaultFontSize).HasColumnName("default_font_size");
|
||||
e.Property(x => x.DefaultFontFace).HasColumnName("default_font_face");
|
||||
e.Property(x => x.IsFontChangeable).HasColumnName("is_font_changeable");
|
||||
e.Property(x => x.IsFontSizeChangeable).HasColumnName("is_font_size_changeable");
|
||||
e.Property(x => x.Justify).HasColumnName("justify");
|
||||
e.Property(x => x.CanJustify).HasColumnName("can_justify");
|
||||
e.Property(x => x.PositionInContainer).HasColumnName("position_in_container");
|
||||
e.Property(x => x.IsTextBox).HasColumnName("is_text_box");
|
||||
e.Property(x => x.MaxSize).HasColumnName("max_size");
|
||||
e.Property(x => x.DirectionLayerKey).HasColumnName("direction_layer_key");
|
||||
e.Property(x => x.DirectionLayerValue).HasColumnName("direction_layer_value");
|
||||
e.Property(x => x.VideoSupport).HasColumnName("video_support");
|
||||
e.Property(x => x.MinDurationSec).HasColumnName("min_duration_sec");
|
||||
e.Property(x => x.MaxDurationSec).HasColumnName("max_duration_sec");
|
||||
e.Property(x => x.Width).HasColumnName("width");
|
||||
e.Property(x => x.Height).HasColumnName("height");
|
||||
e.Property(x => x.Thumbnail).HasColumnName("thumbnail");
|
||||
e.Property(x => x.MappedList).HasColumnName("mapped_list").HasColumnType("jsonb");
|
||||
e.Property(x => x.CounterMode).HasColumnName("counter_mode");
|
||||
e.Property(x => x.AiInputType).HasColumnName("ai_input_type");
|
||||
e.Property(x => x.IsHidden).HasColumnName("is_hidden");
|
||||
e.Property(x => x.IsFocused).HasColumnName("is_focused");
|
||||
e.Property(x => x.Dp1Image).HasColumnName("dp1_image");
|
||||
e.Property(x => x.Dp1Title).HasColumnName("dp1_title");
|
||||
e.Property(x => x.Dp2Image).HasColumnName("dp2_image");
|
||||
e.Property(x => x.Dp2Title).HasColumnName("dp2_title");
|
||||
e.Property(x => x.Dp3Image).HasColumnName("dp3_image");
|
||||
e.Property(x => x.Dp3Title).HasColumnName("dp3_title");
|
||||
e.Property(x => x.Dp4Image).HasColumnName("dp4_image");
|
||||
e.Property(x => x.Dp4Title).HasColumnName("dp4_title");
|
||||
e.Property(x => x.VirtualCount).HasColumnName("virtual_count");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.SharedLayers).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<TemplateSvgPreview>(e =>
|
||||
{
|
||||
e.ToTable("template_svg_previews");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.SourceImageUrl).HasColumnName("source_image_url");
|
||||
e.Property(x => x.SvgUrl).HasColumnName("svg_url").IsRequired();
|
||||
e.Property(x => x.ThumbnailUrl).HasColumnName("thumbnail_url");
|
||||
e.Property(x => x.ColorZones).HasColumnName("color_zones").HasColumnType("jsonb").IsRequired();
|
||||
e.Property(x => x.Width).HasColumnName("width");
|
||||
e.Property(x => x.Height).HasColumnName("height");
|
||||
e.Property(x => x.GenerationMethod).HasColumnName("generation_method");
|
||||
e.Property(x => x.GeneratedByAi).HasColumnName("generated_by_ai");
|
||||
e.Property(x => x.QualityScore).HasColumnName("quality_score");
|
||||
e.Property(x => x.CreatedByUserId).HasColumnName("created_by_user_id");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureCharacters(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<SceneCharacter>(e =>
|
||||
{
|
||||
e.ToTable("scene_characters");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Scene).WithMany(x => x.Characters).HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneCharacterController>(e =>
|
||||
{
|
||||
e.ToTable("scene_character_controllers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.SceneCharacterId).HasColumnName("scene_character_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.DefaultValue).HasColumnName("default_value");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Character).WithMany(x => x.Controllers).HasForeignKey(x => x.SceneCharacterId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<SceneControllerOption>(e =>
|
||||
{
|
||||
e.ToTable("scene_controller_options");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ControllerId).HasColumnName("controller_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.HasOne(x => x.Controller).WithMany(x => x.Options).HasForeignKey(x => x.ControllerId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<ProjectCharacterController>(e =>
|
||||
{
|
||||
e.ToTable("project_character_controllers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.CharacterControllers).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<ProjectCharacterControllerOption>(e =>
|
||||
{
|
||||
e.ToTable("project_character_controller_options");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ControllerId).HasColumnName("controller_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.HasOne(x => x.Controller).WithMany(x => x.Options).HasForeignKey(x => x.ControllerId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<ProjectCharacterPreset>(e =>
|
||||
{
|
||||
e.ToTable("project_character_presets");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Key).HasColumnName("key");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Icon).HasColumnName("icon");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.CharacterPresets).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<PresetCharacterController>(e =>
|
||||
{
|
||||
e.ToTable("preset_character_controllers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.CharacterPresetId).HasColumnName("character_preset_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.HasOne(x => x.Preset).WithMany(x => x.PresetControllers).HasForeignKey(x => x.CharacterPresetId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
mb.Entity<PresetStory>(e =>
|
||||
{
|
||||
e.ToTable("preset_stories");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.ProjectId).HasColumnName("project_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.Demo).HasColumnName("demo");
|
||||
e.Property(x => x.MusicId).HasColumnName("music_id");
|
||||
e.Property(x => x.ScenesSpa).HasColumnName("scenes_spa");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.IsPublished).HasColumnName("is_published");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasOne(x => x.Project).WithMany(x => x.PresetStories).HasForeignKey(x => x.ProjectId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Music).WithMany().HasForeignKey(x => x.MusicId).OnDelete(DeleteBehavior.SetNull);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<PresetScene>(e =>
|
||||
{
|
||||
e.ToTable("preset_scenes");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.PresetStoryId).HasColumnName("preset_story_id");
|
||||
e.Property(x => x.SceneId).HasColumnName("scene_id");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.DefaultDurationSec).HasColumnName("default_duration_sec");
|
||||
e.HasOne(x => x.Story).WithMany(x => x.Scenes).HasForeignKey(x => x.PresetStoryId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Scene).WithMany().HasForeignKey(x => x.SceneId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureCms(ModelBuilder mb)
|
||||
{
|
||||
mb.Entity<Blog>(e =>
|
||||
{
|
||||
e.ToTable("blogs");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Kind).HasColumnName("kind");
|
||||
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.ShortDescription).HasColumnName("short_description");
|
||||
e.Property(x => x.Content).HasColumnName("content").IsRequired();
|
||||
e.Property(x => x.MetaTitle).HasColumnName("meta_title");
|
||||
e.Property(x => x.MetaDescription).HasColumnName("meta_description");
|
||||
e.Property(x => x.MetaKeywords).HasColumnName("meta_keywords");
|
||||
e.Property(x => x.IncludeInSiteMap).HasColumnName("include_in_site_map");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.Cover).HasColumnName("cover");
|
||||
e.Property(x => x.AuthorUserId).HasColumnName("author_user_id");
|
||||
e.Property(x => x.AuthorDisplayName).HasColumnName("author_display_name");
|
||||
e.Property(x => x.IsPublished).HasColumnName("is_published");
|
||||
e.Property(x => x.PublishDate).HasColumnName("publish_date");
|
||||
e.Property(x => x.ViewCount).HasColumnName("view_count");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<Comment>(e =>
|
||||
{
|
||||
e.ToTable("comments");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.UserId).HasColumnName("user_id");
|
||||
e.Property(x => x.BlogId).HasColumnName("blog_id");
|
||||
e.Property(x => x.ContainerId).HasColumnName("container_id");
|
||||
e.Property(x => x.ParentCommentId).HasColumnName("parent_comment_id");
|
||||
e.Property(x => x.Content).HasColumnName("content").IsRequired();
|
||||
e.Property(x => x.Rate).HasColumnName("rate");
|
||||
e.Property(x => x.IsApproved).HasColumnName("is_approved");
|
||||
e.Property(x => x.IsPinned).HasColumnName("is_pinned");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
|
||||
e.HasOne(x => x.Blog).WithMany(x => x.Comments).HasForeignKey(x => x.BlogId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Container).WithMany(x => x.Comments).HasForeignKey(x => x.ContainerId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.ParentComment).WithMany(x => x.Replies).HasForeignKey(x => x.ParentCommentId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasQueryFilter(x => x.DeletedAt == null);
|
||||
});
|
||||
|
||||
mb.Entity<HomePageEvent>(e =>
|
||||
{
|
||||
e.ToTable("home_page_events");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Title).HasColumnName("title");
|
||||
e.Property(x => x.Subtitle).HasColumnName("subtitle");
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.Badge).HasColumnName("badge");
|
||||
e.Property(x => x.BadgeClass).HasColumnName("badge_class");
|
||||
e.Property(x => x.ButtonText).HasColumnName("button_text");
|
||||
e.Property(x => x.ButtonUrl).HasColumnName("button_url");
|
||||
e.Property(x => x.ButtonClass).HasColumnName("button_class");
|
||||
e.Property(x => x.Color).HasColumnName("color");
|
||||
e.Property(x => x.BackgroundColor).HasColumnName("background_color");
|
||||
e.Property(x => x.TextColor).HasColumnName("text_color");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.StartsAt).HasColumnName("starts_at");
|
||||
e.Property(x => x.EndsAt).HasColumnName("ends_at");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<NewSlide>(e =>
|
||||
{
|
||||
e.ToTable("new_slides");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Keyword).HasColumnName("keyword");
|
||||
e.Property(x => x.Title).HasColumnName("title");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.Parameter).HasColumnName("parameter");
|
||||
e.Property(x => x.SlideType).HasColumnName("slide_type");
|
||||
e.Property(x => x.ExpireDate).HasColumnName("expire_date");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<InternalRoute>(e =>
|
||||
{
|
||||
e.ToTable("internal_routes");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Name).HasColumnName("name");
|
||||
e.Property(x => x.Image).HasColumnName("image");
|
||||
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
|
||||
e.Property(x => x.Priority).HasColumnName("priority");
|
||||
e.Property(x => x.LastDate).HasColumnName("last_date");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<CustomRoute>(e =>
|
||||
{
|
||||
e.ToTable("custom_routes");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Target).HasColumnName("target").IsRequired();
|
||||
e.Property(x => x.Destination).HasColumnName("destination").IsRequired();
|
||||
e.Property(x => x.RedirectCode).HasColumnName("redirect_code");
|
||||
e.Property(x => x.IsActive).HasColumnName("is_active");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<WebsiteSetting>(e =>
|
||||
{
|
||||
e.ToTable("website_settings");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
||||
e.Property(x => x.Value).HasColumnName("value").HasColumnType("jsonb").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.IsSecret).HasColumnName("is_secret");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<LearnArticle>(e =>
|
||||
{
|
||||
e.ToTable("learn");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.Body).HasColumnName("body");
|
||||
e.Property(x => x.DemoUrl).HasColumnName("demo_url");
|
||||
e.Property(x => x.Mode).HasColumnName("mode");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<Training>(e =>
|
||||
{
|
||||
e.ToTable("trainings");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Title).HasColumnName("title").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.VideoUrl).HasColumnName("video_url");
|
||||
e.Property(x => x.ThumbnailUrl).HasColumnName("thumbnail_url");
|
||||
e.Property(x => x.Sort).HasColumnName("sort");
|
||||
e.Property(x => x.IsPublished).HasColumnName("is_published");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<FavoriteFolder>(e =>
|
||||
{
|
||||
e.ToTable("favorite_folders");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.UserId).HasColumnName("user_id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
||||
e.Property(x => x.Description).HasColumnName("description");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
||||
});
|
||||
|
||||
mb.Entity<FavoriteContainer>(e =>
|
||||
{
|
||||
e.ToTable("favorite_containers");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).HasColumnName("id");
|
||||
e.Property(x => x.UserId).HasColumnName("user_id");
|
||||
e.Property(x => x.TenantId).HasColumnName("tenant_id");
|
||||
e.Property(x => x.ContainerId).HasColumnName("container_id");
|
||||
e.Property(x => x.FolderId).HasColumnName("folder_id");
|
||||
e.Property(x => x.Note).HasColumnName("note");
|
||||
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
||||
e.HasOne(x => x.Container).WithMany().HasForeignKey(x => x.ContainerId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasOne(x => x.Folder).WithMany(x => x.Containers).HasForeignKey(x => x.FolderId).OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Npgsql;
|
||||
|
||||
namespace FlatRender.ContentSvc.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Npgsql name translator that returns CLR names verbatim. The database enum labels
|
||||
/// match the C# enum member names exactly (e.g. 'FIX', 'MockUp', 'fill', 'LEFT_JUSTIFY'),
|
||||
/// so no snake_case translation may be applied to enum values. PG type names are passed
|
||||
/// explicitly wherever this translator is used, so type-name translation is moot.
|
||||
/// </summary>
|
||||
public sealed class PreserveCaseNameTranslator : INpgsqlNameTranslator
|
||||
{
|
||||
public static readonly PreserveCaseNameTranslator Instance = new();
|
||||
|
||||
public string TranslateTypeName(string clrName) => clrName;
|
||||
|
||||
public string TranslateMemberName(string clrName) => clrName;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Text.Json;
|
||||
using FlatRender.ContentSvc.Models.Responses;
|
||||
|
||||
namespace FlatRender.ContentSvc.Middleware;
|
||||
|
||||
public class ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
};
|
||||
|
||||
public async Task InvokeAsync(HttpContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
await next(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Unhandled exception");
|
||||
await WriteError(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task WriteError(HttpContext ctx, Exception ex)
|
||||
{
|
||||
var (status, code) = ex switch
|
||||
{
|
||||
KeyNotFoundException => (404, "not_found"),
|
||||
UnauthorizedAccessException => (401, "unauthorized"),
|
||||
InvalidOperationException => (400, "invalid_operation"),
|
||||
ArgumentException => (400, "bad_request"),
|
||||
NotImplementedException => (501, "not_implemented"),
|
||||
_ => (500, "internal_error")
|
||||
};
|
||||
|
||||
ctx.Response.StatusCode = status;
|
||||
ctx.Response.ContentType = "application/json";
|
||||
|
||||
var error = new { error = new ApiError(code, ex.Message, ctx.TraceIdentifier) };
|
||||
return ctx.Response.WriteAsync(JsonSerializer.Serialize(error, JsonOptions));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
namespace FlatRender.ContentSvc.Models.Requests;
|
||||
|
||||
// ── Taxonomy ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public record CreateCategoryRequest(
|
||||
Guid? ParentId,
|
||||
string Name,
|
||||
string Slug,
|
||||
string? Description,
|
||||
string? ImageUrl,
|
||||
string? Icon,
|
||||
string? MetaTitle,
|
||||
string? MetaDescription,
|
||||
string? MetaKeywords,
|
||||
bool BotFollow,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record UpdateCategoryRequest(
|
||||
Guid? ParentId,
|
||||
string Name,
|
||||
string Slug,
|
||||
string? Description,
|
||||
string? ImageUrl,
|
||||
string? Icon,
|
||||
string? MetaTitle,
|
||||
string? MetaDescription,
|
||||
string? MetaKeywords,
|
||||
bool BotFollow,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record CreateTagRequest(
|
||||
string Name,
|
||||
string? LatinName,
|
||||
string Slug,
|
||||
string? AppliesToMode,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record UpdateTagRequest(
|
||||
string Name,
|
||||
string? LatinName,
|
||||
string Slug,
|
||||
string? AppliesToMode,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record CreateFontRequest(
|
||||
string Name,
|
||||
string? OriginalName,
|
||||
string? SystemName,
|
||||
string? Family,
|
||||
int? Weight,
|
||||
string? Style,
|
||||
string Direction,
|
||||
string? FileUrl,
|
||||
string? SampleImageUrl,
|
||||
bool IsPremium,
|
||||
bool IsActive,
|
||||
bool InstalledOnNodes,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record UpdateFontRequest(
|
||||
string Name,
|
||||
string? OriginalName,
|
||||
string? SystemName,
|
||||
string? Family,
|
||||
int? Weight,
|
||||
string? Style,
|
||||
string Direction,
|
||||
string? FileUrl,
|
||||
string? SampleImageUrl,
|
||||
bool IsPremium,
|
||||
bool IsActive,
|
||||
bool InstalledOnNodes,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record CreateMusicTrackRequest(
|
||||
string Name,
|
||||
string? Caption,
|
||||
string? Keywords,
|
||||
string Url,
|
||||
string? WaveformData,
|
||||
decimal DurationSec,
|
||||
int? Bpm,
|
||||
string? Genre,
|
||||
string? Mood,
|
||||
bool IsPremium,
|
||||
bool IsActive,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record UpdateMusicTrackRequest(
|
||||
string Name,
|
||||
string? Caption,
|
||||
string? Keywords,
|
||||
string Url,
|
||||
string? WaveformData,
|
||||
decimal DurationSec,
|
||||
int? Bpm,
|
||||
string? Genre,
|
||||
string? Mood,
|
||||
bool IsPremium,
|
||||
bool IsActive,
|
||||
int Sort
|
||||
);
|
||||
|
||||
// ── Templates ────────────────────────────────────────────────────────────────
|
||||
|
||||
public record CreateContainerRequest(
|
||||
string Slug,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Keywords,
|
||||
string? NewsText,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? FullDemo,
|
||||
string? MiniDemo,
|
||||
string? DemoScriptTag,
|
||||
bool IsPublished,
|
||||
bool IsPremium,
|
||||
bool IsMockup,
|
||||
string PrimaryMode,
|
||||
int Sort,
|
||||
List<Guid> CategoryIds,
|
||||
List<Guid> TagIds
|
||||
);
|
||||
|
||||
public record UpdateContainerRequest(
|
||||
string Slug,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Keywords,
|
||||
string? NewsText,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? FullDemo,
|
||||
string? MiniDemo,
|
||||
string? DemoScriptTag,
|
||||
bool IsPublished,
|
||||
bool IsPremium,
|
||||
bool IsMockup,
|
||||
string PrimaryMode,
|
||||
int Sort,
|
||||
List<Guid> CategoryIds,
|
||||
List<Guid> TagIds
|
||||
);
|
||||
|
||||
public record ContainerListRequest(
|
||||
int Page = 1,
|
||||
int PageSize = 20,
|
||||
string? Search = null,
|
||||
Guid? CategoryId = null,
|
||||
string? TagSlug = null,
|
||||
bool? IsPublished = null,
|
||||
bool? IsPremium = null,
|
||||
string? Mode = null,
|
||||
string? Sort = "sort_date_desc"
|
||||
);
|
||||
|
||||
public record CreateProjectRequest(
|
||||
Guid ContainerId,
|
||||
Guid? ProjectServerId,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Image,
|
||||
string? FullDemo,
|
||||
string? DemoScriptTag,
|
||||
string? DownloadLink,
|
||||
string? Folder,
|
||||
int OriginalWidth,
|
||||
int OriginalHeight,
|
||||
string? Aspect,
|
||||
decimal ProjectDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int FreeFps,
|
||||
string ChooseMode,
|
||||
string Resolution,
|
||||
decimal VipFactor,
|
||||
string RenderAepComp,
|
||||
bool IsPublished,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record UpdateProjectRequest(
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Image,
|
||||
string? FullDemo,
|
||||
string? DemoScriptTag,
|
||||
string? DownloadLink,
|
||||
string? Folder,
|
||||
int OriginalWidth,
|
||||
int OriginalHeight,
|
||||
string? Aspect,
|
||||
decimal ProjectDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int FreeFps,
|
||||
string ChooseMode,
|
||||
string Resolution,
|
||||
decimal VipFactor,
|
||||
string RenderAepComp,
|
||||
string? SharedLayerImage,
|
||||
string? SharedColorsSvg,
|
||||
string? SharedColorPresetsSvg,
|
||||
bool IsPublished,
|
||||
int Sort
|
||||
);
|
||||
|
||||
// ── CMS ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
public record CreateBlogRequest(
|
||||
string Slug,
|
||||
string Title,
|
||||
string? ShortDescription,
|
||||
string Content,
|
||||
string? MetaTitle,
|
||||
string? MetaDescription,
|
||||
string? MetaKeywords,
|
||||
bool IncludeInSiteMap,
|
||||
string? Image,
|
||||
string? Cover,
|
||||
string? AuthorDisplayName,
|
||||
bool IsPublished,
|
||||
DateTime? PublishDate,
|
||||
string Kind = "Blog"
|
||||
);
|
||||
|
||||
public record UpdateBlogRequest(
|
||||
string Slug,
|
||||
string Title,
|
||||
string? ShortDescription,
|
||||
string Content,
|
||||
string? MetaTitle,
|
||||
string? MetaDescription,
|
||||
string? MetaKeywords,
|
||||
bool IncludeInSiteMap,
|
||||
string? Image,
|
||||
string? Cover,
|
||||
string? AuthorDisplayName,
|
||||
bool IsPublished,
|
||||
DateTime? PublishDate
|
||||
);
|
||||
|
||||
public record BlogListRequest(
|
||||
int Page = 1,
|
||||
int PageSize = 20,
|
||||
string? Search = null,
|
||||
bool? IsPublished = null,
|
||||
string Kind = "Blog"
|
||||
);
|
||||
|
||||
public record CreateCommentRequest(
|
||||
Guid? BlogId,
|
||||
Guid? ContainerId,
|
||||
Guid? ParentCommentId,
|
||||
string Content,
|
||||
decimal? Rate
|
||||
);
|
||||
|
||||
public record CreateSlideRequest(
|
||||
string? Keyword,
|
||||
string? Title,
|
||||
string? Image,
|
||||
string? Parameter,
|
||||
string SlideType,
|
||||
DateTime? ExpireDate,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record UpdateSlideRequest(
|
||||
string? Keyword,
|
||||
string? Title,
|
||||
string? Image,
|
||||
string? Parameter,
|
||||
string SlideType,
|
||||
DateTime? ExpireDate,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record UpsertWebsiteSettingRequest(
|
||||
string Key,
|
||||
string Value,
|
||||
string? Description,
|
||||
bool IsSecret
|
||||
);
|
||||
|
||||
public record CreateFavoriteFolderRequest(string Name, string? Description);
|
||||
public record UpdateFavoriteFolderRequest(string Name, string? Description);
|
||||
public record AddFavoriteContainerRequest(Guid ContainerId, Guid? FolderId, string? Note);
|
||||
@@ -0,0 +1,452 @@
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
|
||||
namespace FlatRender.ContentSvc.Models.Responses;
|
||||
|
||||
// ── Pagination ───────────────────────────────────────────────────────────────
|
||||
|
||||
public record PagedResponse<T>(IEnumerable<T> Items, PaginationMeta Meta);
|
||||
public record PaginationMeta(int Page, int PageSize, long Total, int TotalPages);
|
||||
public record ApiError(string Code, string Message, string? TraceId = null);
|
||||
|
||||
// ── Taxonomy ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public record CategoryResponse(
|
||||
Guid Id,
|
||||
Guid? ParentId,
|
||||
string Name,
|
||||
string Slug,
|
||||
string? Description,
|
||||
string? ImageUrl,
|
||||
string? Icon,
|
||||
bool IsActive,
|
||||
int Sort,
|
||||
List<CategoryResponse> Children
|
||||
);
|
||||
|
||||
public record TagResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? LatinName,
|
||||
string Slug,
|
||||
string? AppliesToMode,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record FontResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? OriginalName,
|
||||
string? SystemName,
|
||||
string? Family,
|
||||
int? Weight,
|
||||
string? Style,
|
||||
string Direction,
|
||||
string? FileUrl,
|
||||
string? SampleImageUrl,
|
||||
bool IsPremium,
|
||||
bool IsActive,
|
||||
bool InstalledOnNodes
|
||||
);
|
||||
|
||||
public record MusicTrackResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Caption,
|
||||
string Url,
|
||||
string? WaveformData,
|
||||
decimal DurationSec,
|
||||
int? Bpm,
|
||||
string? Genre,
|
||||
string? Mood,
|
||||
bool IsPremium
|
||||
);
|
||||
|
||||
// ── Templates ────────────────────────────────────────────────────────────────
|
||||
|
||||
public record ContainerSummaryResponse(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? MiniDemo,
|
||||
bool IsPublished,
|
||||
bool IsPremium,
|
||||
bool IsMockup,
|
||||
string PrimaryMode,
|
||||
decimal? RateAvg,
|
||||
int RateCount,
|
||||
long ViewCount,
|
||||
long UseCount,
|
||||
int Sort,
|
||||
DateTime SortDate,
|
||||
List<string> CategorySlugs,
|
||||
List<string> Tags
|
||||
);
|
||||
|
||||
public record ContainerDetailResponse(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Keywords,
|
||||
string? NewsText,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? FullDemo,
|
||||
string? MiniDemo,
|
||||
string? DemoScriptTag,
|
||||
bool IsPublished,
|
||||
bool IsPremium,
|
||||
bool IsMockup,
|
||||
string PrimaryMode,
|
||||
decimal? RateAvg,
|
||||
int RateCount,
|
||||
long ViewCount,
|
||||
long UseCount,
|
||||
int Sort,
|
||||
DateTime SortDate,
|
||||
List<ProjectResponse> Projects,
|
||||
List<CategoryResponse> Categories,
|
||||
List<TagResponse> Tags
|
||||
);
|
||||
|
||||
public record ProjectResponse(
|
||||
Guid Id,
|
||||
Guid ContainerId,
|
||||
string Name,
|
||||
string? Image,
|
||||
string? FullDemo,
|
||||
int OriginalWidth,
|
||||
int OriginalHeight,
|
||||
string? Aspect,
|
||||
decimal ProjectDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int FreeFps,
|
||||
string ChooseMode,
|
||||
string Resolution,
|
||||
bool IsPublished,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record ProjectDetailResponse(
|
||||
Guid Id,
|
||||
Guid ContainerId,
|
||||
string Name,
|
||||
string? Description,
|
||||
string? Image,
|
||||
string? FullDemo,
|
||||
string? DemoScriptTag,
|
||||
string? DownloadLink,
|
||||
int OriginalWidth,
|
||||
int OriginalHeight,
|
||||
string? Aspect,
|
||||
decimal ProjectDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int FreeFps,
|
||||
string ChooseMode,
|
||||
string Resolution,
|
||||
decimal VipFactor,
|
||||
string RenderAepComp,
|
||||
string? SharedLayerImage,
|
||||
bool IsPublished,
|
||||
int Sort,
|
||||
List<SceneResponse> Scenes,
|
||||
List<SharedColorResponse> SharedColors,
|
||||
List<SharedLayerResponse> SharedLayers
|
||||
);
|
||||
|
||||
// ── Scenes ───────────────────────────────────────────────────────────────────
|
||||
|
||||
public record SceneResponse(
|
||||
Guid Id,
|
||||
Guid ProjectId,
|
||||
string Key,
|
||||
string Title,
|
||||
string? LocalizedTitle,
|
||||
string SceneType,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? SnapshotUrl,
|
||||
bool GenerateKf,
|
||||
decimal? DefaultDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
decimal OverlapAtEndSec,
|
||||
bool CanHandleDuration,
|
||||
bool ManualColorSelection,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record SceneDetailResponse(
|
||||
Guid Id,
|
||||
Guid ProjectId,
|
||||
string Key,
|
||||
string Title,
|
||||
string? LocalizedTitle,
|
||||
string SceneType,
|
||||
string? Image,
|
||||
string? Demo,
|
||||
string? SnapshotUrl,
|
||||
bool GenerateKf,
|
||||
decimal? DefaultDurationSec,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
decimal OverlapAtEndSec,
|
||||
bool CanHandleDuration,
|
||||
bool ManualColorSelection,
|
||||
int Sort,
|
||||
bool IsActive,
|
||||
List<RepeaterItemResponse> RepeaterItems,
|
||||
List<ContentElementResponse> ContentElements,
|
||||
List<ColorElementResponse> ColorElements,
|
||||
List<ColorPresetResponse> ColorPresets,
|
||||
List<CharacterResponse> Characters
|
||||
);
|
||||
|
||||
public record RepeaterItemResponse(
|
||||
Guid Id,
|
||||
string Title,
|
||||
string RepeatBoxKey,
|
||||
string RepeatItemKey,
|
||||
int MaxRepeatCount,
|
||||
bool UserCanChangeSort,
|
||||
string RepeatSortStrategy,
|
||||
int Sort,
|
||||
List<ContentElementResponse> ContentElements
|
||||
);
|
||||
|
||||
public record ContentElementResponse(
|
||||
Guid Id,
|
||||
Guid? RepeaterItemId,
|
||||
string Key,
|
||||
string Title,
|
||||
string? LocalizedTitle,
|
||||
string? Hint,
|
||||
string Type,
|
||||
string? DefaultValue,
|
||||
Guid? FontId,
|
||||
string? FontFace,
|
||||
string? FontFaceName,
|
||||
int? FontSize,
|
||||
int? DefaultFontSize,
|
||||
string? DefaultFontFace,
|
||||
bool IsFontChangeable,
|
||||
bool IsFontSizeChangeable,
|
||||
string Justify,
|
||||
bool CanJustify,
|
||||
int PositionInContainer,
|
||||
bool IsTextBox,
|
||||
int? MaxSize,
|
||||
string? DirectionLayerKey,
|
||||
int DirectionLayerValue,
|
||||
bool VideoSupport,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int? Width,
|
||||
int? Height,
|
||||
string? Thumbnail,
|
||||
string? MappedList,
|
||||
string? CounterMode,
|
||||
string AiInputType,
|
||||
bool IsHidden,
|
||||
bool IsFocused,
|
||||
string? OpacityControllerKey,
|
||||
int VirtualCount,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record ColorElementResponse(
|
||||
Guid Id,
|
||||
string ElementKey,
|
||||
string Title,
|
||||
string? Icon,
|
||||
string AttrValue,
|
||||
string DefaultColor,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record ColorPresetResponse(
|
||||
Guid Id,
|
||||
string? Name,
|
||||
int Sort,
|
||||
List<ColorPresetItemResponse> Items
|
||||
);
|
||||
|
||||
public record ColorPresetItemResponse(
|
||||
Guid Id,
|
||||
string ElementKey,
|
||||
string Value,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record SharedColorResponse(
|
||||
Guid Id,
|
||||
string ElementKey,
|
||||
string Title,
|
||||
string? Icon,
|
||||
string AttrValue,
|
||||
string DefaultColor,
|
||||
int Sort
|
||||
);
|
||||
|
||||
public record SharedLayerResponse(
|
||||
Guid Id,
|
||||
string Key,
|
||||
string Title,
|
||||
string? LocalizedTitle,
|
||||
string? Hint,
|
||||
string Type,
|
||||
string? DefaultValue,
|
||||
Guid? FontId,
|
||||
string? FontFace,
|
||||
int? FontSize,
|
||||
bool IsFontChangeable,
|
||||
bool IsFontSizeChangeable,
|
||||
string Justify,
|
||||
bool CanJustify,
|
||||
int PositionInContainer,
|
||||
bool IsTextBox,
|
||||
int? MaxSize,
|
||||
bool VideoSupport,
|
||||
decimal? MinDurationSec,
|
||||
decimal? MaxDurationSec,
|
||||
int? Width,
|
||||
int? Height,
|
||||
string? MappedList,
|
||||
string AiInputType,
|
||||
bool IsHidden,
|
||||
bool IsFocused,
|
||||
int VirtualCount,
|
||||
int Sort
|
||||
);
|
||||
|
||||
// ── Characters ───────────────────────────────────────────────────────────────
|
||||
|
||||
public record CharacterResponse(
|
||||
Guid Id,
|
||||
string Key,
|
||||
string Name,
|
||||
string? Icon,
|
||||
int Sort,
|
||||
List<CharacterControllerResponse> Controllers
|
||||
);
|
||||
|
||||
public record CharacterControllerResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string Key,
|
||||
string? DefaultValue,
|
||||
int Sort,
|
||||
List<ControllerOptionResponse> Options
|
||||
);
|
||||
|
||||
public record ControllerOptionResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Icon,
|
||||
string Value,
|
||||
int Sort
|
||||
);
|
||||
|
||||
// ── CMS ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
public record BlogSummaryResponse(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Title,
|
||||
string? ShortDescription,
|
||||
string? Image,
|
||||
string? Cover,
|
||||
string? AuthorDisplayName,
|
||||
bool IsPublished,
|
||||
DateTime? PublishDate,
|
||||
long ViewCount,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
public record BlogDetailResponse(
|
||||
Guid Id,
|
||||
string Slug,
|
||||
string Title,
|
||||
string? ShortDescription,
|
||||
string Content,
|
||||
string? MetaTitle,
|
||||
string? MetaDescription,
|
||||
string? MetaKeywords,
|
||||
bool IncludeInSiteMap,
|
||||
string? Image,
|
||||
string? Cover,
|
||||
Guid? AuthorUserId,
|
||||
string? AuthorDisplayName,
|
||||
bool IsPublished,
|
||||
DateTime? PublishDate,
|
||||
long ViewCount,
|
||||
DateTime CreatedAt,
|
||||
DateTime UpdatedAt
|
||||
);
|
||||
|
||||
public record CommentResponse(
|
||||
Guid Id,
|
||||
Guid UserId,
|
||||
Guid? BlogId,
|
||||
Guid? ContainerId,
|
||||
Guid? ParentCommentId,
|
||||
string Content,
|
||||
decimal? Rate,
|
||||
bool IsApproved,
|
||||
bool IsPinned,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
public record SlideResponse(
|
||||
Guid Id,
|
||||
string? Keyword,
|
||||
string? Title,
|
||||
string? Image,
|
||||
string? Parameter,
|
||||
string SlideType,
|
||||
DateTime? ExpireDate,
|
||||
int Sort,
|
||||
bool IsActive
|
||||
);
|
||||
|
||||
public record HomePageEventResponse(
|
||||
Guid Id,
|
||||
string? Title,
|
||||
string? Subtitle,
|
||||
string? Description,
|
||||
string? Badge,
|
||||
string? BadgeClass,
|
||||
string? ButtonText,
|
||||
string? ButtonUrl,
|
||||
string? ButtonClass,
|
||||
string? Color,
|
||||
string? BackgroundColor,
|
||||
string? TextColor,
|
||||
string? Image,
|
||||
bool IsActive,
|
||||
int Sort,
|
||||
DateTime? StartsAt,
|
||||
DateTime? EndsAt
|
||||
);
|
||||
|
||||
public record WebsiteSettingResponse(
|
||||
Guid Id,
|
||||
string Key,
|
||||
string Value,
|
||||
string? Description,
|
||||
bool IsSecret
|
||||
);
|
||||
|
||||
public record FavoriteFolderResponse(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Description,
|
||||
int ContainerCount,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
@@ -0,0 +1,127 @@
|
||||
using System.Text;
|
||||
using FlatRender.ContentSvc.Application.Services;
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
using FlatRender.ContentSvc.Infrastructure.Data;
|
||||
using FlatRender.ContentSvc.Middleware;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Npgsql;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// ── Database ──────────────────────────────────────────────────────────────────
|
||||
// Native PostgreSQL enums are mapped on the EF provider so Npgsql can read/write
|
||||
// them at runtime (HasPostgresEnum in the model alone is not enough on Npgsql 8+).
|
||||
// PG labels match the C# enum member names exactly, so preserve case verbatim.
|
||||
var enumTr = PreserveCaseNameTranslator.Instance;
|
||||
builder.Services.AddDbContext<ContentDbContext>(options =>
|
||||
options.UseNpgsql(
|
||||
builder.Configuration.GetConnectionString("Postgres"),
|
||||
npgsql =>
|
||||
{
|
||||
npgsql.MapEnum<ChooseMode>("choose_mode", "content", enumTr);
|
||||
npgsql.MapEnum<ResolutionKind>("resolution_kind", "content", enumTr);
|
||||
npgsql.MapEnum<SceneKind>("scene_kind", "content", enumTr);
|
||||
npgsql.MapEnum<ContentElementType>("content_element_type", "content", enumTr);
|
||||
npgsql.MapEnum<JustifyKind>("justify_kind", "content", enumTr);
|
||||
npgsql.MapEnum<AiInputType>("ai_input_type", "content", enumTr);
|
||||
npgsql.MapEnum<RepeatSortStrategy>("repeat_sort_strategy", "content", enumTr);
|
||||
npgsql.MapEnum<AttrValueKind>("attr_value_kind", "content", enumTr);
|
||||
npgsql.MapEnum<BlogKind>("blog_kind", "content", enumTr);
|
||||
npgsql.MapEnum<SlideType>("slide_type", "content", enumTr);
|
||||
})
|
||||
.UseSnakeCaseNamingConvention());
|
||||
|
||||
// ── JWT Auth ──────────────────────────────────────────────────────────────────
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!))
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
// ── Application Services ──────────────────────────────────────────────────────
|
||||
|
||||
builder.Services.AddScoped<TaxonomyService>();
|
||||
builder.Services.AddScoped<TemplateService>();
|
||||
builder.Services.AddScoped<CmsService>();
|
||||
|
||||
// ── HTTP ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
builder.Services.AddRouting(opts =>
|
||||
{
|
||||
opts.LowercaseUrls = true;
|
||||
opts.AppendTrailingSlash = false; // prevent 301 redirects from gateway calls
|
||||
});
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(opts =>
|
||||
{
|
||||
opts.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower;
|
||||
});
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "FlatRender Content API", Version = "v1" });
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "JWT Bearer token"
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } },
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddCheck("db", () => HealthCheckResult.Healthy());
|
||||
|
||||
builder.Services.AddCors(opts => opts.AddDefaultPolicy(p =>
|
||||
p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
|
||||
|
||||
// ── Build ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
using var scope = app.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ContentDbContext>();
|
||||
db.Database.Migrate();
|
||||
}
|
||||
|
||||
app.UseCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapHealthChecks("/health", new HealthCheckOptions { AllowCachingResponses = false });
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5088",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7250;http://localhost:5088",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"Postgres": "Host=localhost;Port=5432;Database=flatrender;Username=postgres;Password=postgres;Search Path=content,public"
|
||||
},
|
||||
"Jwt": {
|
||||
"Secret": "your-256-bit-secret-key-change-in-production",
|
||||
"Issuer": "flatrender",
|
||||
"Audience": "flatrender",
|
||||
"AccessTokenExpiryMinutes": 15,
|
||||
"RefreshTokenExpiryDays": 30
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.AspNetCore": "Information",
|
||||
"Microsoft.EntityFrameworkCore": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
|
||||
<add key="nuget.org" value="http://171.22.25.73:8081/repository/nuget-group/index.json" protocolVersion="3" allowInsecureConnections="true" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -0,0 +1,30 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: flatrender
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pg_content_data:/var/lib/postgresql/data
|
||||
- ../../backend/db/migrations:/docker-entrypoint-initdb.d:ro
|
||||
|
||||
content-svc:
|
||||
build: .
|
||||
ports:
|
||||
- "5011:8080"
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
ConnectionStrings__Postgres: "Host=postgres;Port=5432;Database=flatrender;Username=postgres;Password=postgres;Search Path=content,public"
|
||||
Jwt__Secret: "dev-secret-32-chars-minimum-here!!"
|
||||
Jwt__Issuer: "flatrender"
|
||||
Jwt__Audience: "flatrender"
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
volumes:
|
||||
pg_content_data:
|
||||
Reference in New Issue
Block a user