90ac0b81d1
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>
293 lines
18 KiB
C#
293 lines
18 KiB
C#
using FlatRender.StudioSvc.Domain.Entities;
|
|
using FlatRender.StudioSvc.Domain.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FlatRender.StudioSvc.Infrastructure.Data;
|
|
|
|
public class StudioDbContext(DbContextOptions<StudioDbContext> options) : DbContext(options)
|
|
{
|
|
public DbSet<SavedProject> SavedProjects => Set<SavedProject>();
|
|
public DbSet<SavedScene> SavedScenes => Set<SavedScene>();
|
|
public DbSet<SavedSceneContent> SavedSceneContents => Set<SavedSceneContent>();
|
|
public DbSet<SavedSceneColor> SavedSceneColors => Set<SavedSceneColor>();
|
|
public DbSet<SavedSceneColorPreset> SavedSceneColorPresets => Set<SavedSceneColorPreset>();
|
|
public DbSet<SavedSceneColorPresetItem> SavedSceneColorPresetItems => Set<SavedSceneColorPresetItem>();
|
|
public DbSet<SavedSceneCharacter> SavedSceneCharacters => Set<SavedSceneCharacter>();
|
|
public DbSet<SavedSceneCharacterController> SavedSceneCharacterControllers => Set<SavedSceneCharacterController>();
|
|
public DbSet<SavedSharedColor> SavedSharedColors => Set<SavedSharedColor>();
|
|
public DbSet<SavedSharedColorPreset> SavedSharedColorPresets => Set<SavedSharedColorPreset>();
|
|
public DbSet<SavedSharedColorPresetItem> SavedSharedColorPresetItems => Set<SavedSharedColorPresetItem>();
|
|
public DbSet<SavedSharedLayer> SavedSharedLayers => Set<SavedSharedLayer>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder mb)
|
|
{
|
|
mb.HasDefaultSchema("studio");
|
|
// Native PostgreSQL enum registered on the EF provider via npgsql.MapEnum<T>()
|
|
// in Program.cs (EF Core 9+ approach), covering model + runtime ADO mapping.
|
|
|
|
ConfigureSavedProjects(mb);
|
|
ConfigureSavedScenes(mb);
|
|
ConfigureSharedData(mb);
|
|
}
|
|
|
|
private static void ConfigureSavedProjects(ModelBuilder mb)
|
|
{
|
|
mb.Entity<SavedProject>(e =>
|
|
{
|
|
e.ToTable("saved_projects");
|
|
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.OriginalProjectId).HasColumnName("original_project_id");
|
|
e.Property(x => x.OriginalProjectName).HasColumnName("original_project_name").IsRequired();
|
|
e.Property(x => x.OriginalContainerId).HasColumnName("original_container_id");
|
|
e.Property(x => x.OriginalContainerSlug).HasColumnName("original_container_slug").HasColumnType("citext");
|
|
e.Property(x => x.Name).HasColumnName("name").IsRequired();
|
|
e.Property(x => x.Image).HasColumnName("image");
|
|
e.Property(x => x.Type).HasColumnName("type");
|
|
e.Property(x => x.FrameRate).HasColumnName("frame_rate");
|
|
e.Property(x => x.ProjectDurationSec).HasColumnName("project_duration_sec");
|
|
e.Property(x => x.Resolution).HasColumnName("resolution").IsRequired();
|
|
e.Property(x => x.ChooseMode).HasColumnName("choose_mode").IsRequired();
|
|
e.Property(x => x.VipFactor).HasColumnName("vip_factor");
|
|
e.Property(x => x.MusicFileId).HasColumnName("music_file_id");
|
|
e.Property(x => x.MusicTrackId).HasColumnName("music_track_id");
|
|
e.Property(x => x.MusicVolume).HasColumnName("music_volume");
|
|
e.Property(x => x.VoiceoverFileId).HasColumnName("voiceover_file_id");
|
|
e.Property(x => x.VoiceoverVolume).HasColumnName("voiceover_volume");
|
|
e.Property(x => x.VoiceoverRecordedInBrowser).HasColumnName("voiceover_recorded_in_browser");
|
|
e.Property(x => x.SfxVolume).HasColumnName("sfx_volume");
|
|
e.Property(x => x.SfxEnabled).HasColumnName("sfx_enabled");
|
|
e.Property(x => x.AudioVisualizerMusicUrl).HasColumnName("audio_visualizer_music_url");
|
|
e.Property(x => x.AudioVisualizerDurationSec).HasColumnName("audio_visualizer_duration_sec");
|
|
e.Property(x => x.ManualColorPicker).HasColumnName("manual_color_picker");
|
|
e.Property(x => x.SelectedPresetStoryId).HasColumnName("selected_preset_story_id");
|
|
e.Property(x => x.LastEditStep).HasColumnName("last_edit_step");
|
|
e.Property(x => x.EditState).HasColumnName("edit_state").HasColumnType("jsonb").IsRequired();
|
|
e.Property(x => x.CreateDate).HasColumnName("create_date");
|
|
e.Property(x => x.LastEditDate).HasColumnName("last_edit_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);
|
|
});
|
|
}
|
|
|
|
private static void ConfigureSavedScenes(ModelBuilder mb)
|
|
{
|
|
mb.Entity<SavedScene>(e =>
|
|
{
|
|
e.ToTable("saved_scenes");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedProjectId).HasColumnName("saved_project_id");
|
|
e.Property(x => x.OriginalSceneId).HasColumnName("original_scene_id");
|
|
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
|
e.Property(x => x.Title).HasColumnName("title");
|
|
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.SceneType).HasColumnName("scene_type").IsRequired();
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.Property(x => x.SceneLengthSec).HasColumnName("scene_length_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.SelectedColorPresetId).HasColumnName("selected_color_preset_id");
|
|
e.Property(x => x.CreatedAt).HasColumnName("created_at");
|
|
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
|
|
e.HasOne(x => x.SavedProject).WithMany(x => x.Scenes).HasForeignKey(x => x.SavedProjectId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSceneContent>(e =>
|
|
{
|
|
e.ToTable("saved_scene_contents");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedSceneId).HasColumnName("saved_scene_id");
|
|
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
|
e.Property(x => x.Title).HasColumnName("title");
|
|
e.Property(x => x.LocalizedTitle).HasColumnName("localized_title").HasColumnType("jsonb");
|
|
e.Property(x => x.Hint).HasColumnName("hint");
|
|
e.Property(x => x.Type).HasColumnName("type").IsRequired();
|
|
e.Property(x => x.Value).HasColumnName("value");
|
|
e.Property(x => x.ValueFileId).HasColumnName("value_file_id");
|
|
e.Property(x => x.InsertedFileType).HasColumnName("inserted_file_type");
|
|
e.Property(x => x.FileUrlCached).HasColumnName("file_url_cached");
|
|
e.Property(x => x.FileUrlCachedAt).HasColumnName("file_url_cached_at");
|
|
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.Justify).HasColumnName("justify");
|
|
e.Property(x => x.PositionInContainer).HasColumnName("position_in_container");
|
|
e.Property(x => x.DirectionLayerValue).HasColumnName("direction_layer_value");
|
|
e.Property(x => x.IsTextBox).HasColumnName("is_text_box");
|
|
e.Property(x => x.AiInputType).HasColumnName("ai_input_type");
|
|
e.Property(x => x.SelectedDp).HasColumnName("selected_dp");
|
|
e.Property(x => x.RepeaterItemKey).HasColumnName("repeater_item_key");
|
|
e.Property(x => x.RepeaterIndex).HasColumnName("repeater_index");
|
|
e.Property(x => x.IsFocused).HasColumnName("is_focused");
|
|
e.Property(x => x.Status).HasColumnName("status");
|
|
e.Property(x => x.MappedList).HasColumnName("mapped_list").HasColumnType("jsonb");
|
|
e.Property(x => x.Thumbnail).HasColumnName("thumbnail");
|
|
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.SavedScene).WithMany(x => x.Contents).HasForeignKey(x => x.SavedSceneId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSceneColor>(e =>
|
|
{
|
|
e.ToTable("saved_scene_colors");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedSceneId).HasColumnName("saved_scene_id");
|
|
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
|
e.Property(x => x.Title).HasColumnName("title");
|
|
e.Property(x => x.Icon).HasColumnName("icon");
|
|
e.Property(x => x.AttrValue).HasColumnName("attr_value").IsRequired();
|
|
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
|
e.Property(x => x.IsSelected).HasColumnName("is_selected");
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.HasOne(x => x.SavedScene).WithMany(x => x.Colors).HasForeignKey(x => x.SavedSceneId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSceneColorPreset>(e =>
|
|
{
|
|
e.ToTable("saved_scene_color_presets");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedSceneId).HasColumnName("saved_scene_id");
|
|
e.Property(x => x.IsSelected).HasColumnName("is_selected");
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.HasOne(x => x.SavedScene).WithMany(x => x.ColorPresets).HasForeignKey(x => x.SavedSceneId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSceneColorPresetItem>(e =>
|
|
{
|
|
e.ToTable("saved_scene_color_preset_items");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
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<SavedSceneCharacter>(e =>
|
|
{
|
|
e.ToTable("saved_scene_characters");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedSceneId).HasColumnName("saved_scene_id");
|
|
e.Property(x => x.Key).HasColumnName("key");
|
|
e.Property(x => x.Name).HasColumnName("name");
|
|
e.Property(x => x.Icon).HasColumnName("icon");
|
|
e.HasOne(x => x.SavedScene).WithMany(x => x.Characters).HasForeignKey(x => x.SavedSceneId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSceneCharacterController>(e =>
|
|
{
|
|
e.ToTable("saved_scene_character_controllers");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedSceneCharacterId).HasColumnName("saved_scene_character_id");
|
|
e.Property(x => x.Name).HasColumnName("name");
|
|
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
|
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.HasOne(x => x.Character).WithMany(x => x.Controllers).HasForeignKey(x => x.SavedSceneCharacterId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
}
|
|
|
|
private static void ConfigureSharedData(ModelBuilder mb)
|
|
{
|
|
mb.Entity<SavedSharedColor>(e =>
|
|
{
|
|
e.ToTable("saved_shared_colors");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedProjectId).HasColumnName("saved_project_id");
|
|
e.Property(x => x.ElementKey).HasColumnName("element_key").IsRequired();
|
|
e.Property(x => x.Title).HasColumnName("title");
|
|
e.Property(x => x.Icon).HasColumnName("icon");
|
|
e.Property(x => x.AttrValue).HasColumnName("attr_value").IsRequired();
|
|
e.Property(x => x.Value).HasColumnName("value").IsRequired();
|
|
e.Property(x => x.IsSelected).HasColumnName("is_selected");
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.HasOne(x => x.SavedProject).WithMany(x => x.SharedColors).HasForeignKey(x => x.SavedProjectId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSharedColorPreset>(e =>
|
|
{
|
|
e.ToTable("saved_shared_color_presets");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedProjectId).HasColumnName("saved_project_id");
|
|
e.Property(x => x.Name).HasColumnName("name");
|
|
e.Property(x => x.IsSelected).HasColumnName("is_selected");
|
|
e.Property(x => x.Sort).HasColumnName("sort");
|
|
e.HasOne(x => x.SavedProject).WithMany(x => x.SharedColorPresets).HasForeignKey(x => x.SavedProjectId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
mb.Entity<SavedSharedColorPresetItem>(e =>
|
|
{
|
|
e.ToTable("saved_shared_color_preset_items");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
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<SavedSharedLayer>(e =>
|
|
{
|
|
e.ToTable("saved_shared_layers");
|
|
e.HasKey(x => x.Id);
|
|
e.Property(x => x.Id).HasColumnName("id").UseIdentityByDefaultColumn();
|
|
e.Property(x => x.SavedProjectId).HasColumnName("saved_project_id");
|
|
e.Property(x => x.Key).HasColumnName("key").IsRequired();
|
|
e.Property(x => x.Title).HasColumnName("title");
|
|
e.Property(x => x.LocalizedTitle).HasColumnName("localized_title").HasColumnType("jsonb");
|
|
e.Property(x => x.Hint).HasColumnName("hint");
|
|
e.Property(x => x.Type).HasColumnName("type").IsRequired();
|
|
e.Property(x => x.Value).HasColumnName("value");
|
|
e.Property(x => x.ValueFileId).HasColumnName("value_file_id");
|
|
e.Property(x => x.FileUrlCached).HasColumnName("file_url_cached");
|
|
e.Property(x => x.FileUrlCachedAt).HasColumnName("file_url_cached_at");
|
|
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.Justify).HasColumnName("justify");
|
|
e.Property(x => x.PositionInContainer).HasColumnName("position_in_container");
|
|
e.Property(x => x.DirectionLayerValue).HasColumnName("direction_layer_value");
|
|
e.Property(x => x.IsTextBox).HasColumnName("is_text_box");
|
|
e.Property(x => x.AiInputType).HasColumnName("ai_input_type");
|
|
e.Property(x => x.MappedList).HasColumnName("mapped_list").HasColumnType("jsonb");
|
|
e.Property(x => x.Thumbnail).HasColumnName("thumbnail");
|
|
e.Property(x => x.Width).HasColumnName("width");
|
|
e.Property(x => x.Height).HasColumnName("height");
|
|
e.Property(x => x.MinDurationSec).HasColumnName("min_duration_sec");
|
|
e.Property(x => x.MaxDurationSec).HasColumnName("max_duration_sec");
|
|
e.Property(x => x.IsFocused).HasColumnName("is_focused");
|
|
e.Property(x => x.IsFontChangeable).HasColumnName("is_font_changeable");
|
|
e.Property(x => x.IsFontSizeChangeable).HasColumnName("is_font_size_changeable");
|
|
e.Property(x => x.Status).HasColumnName("status");
|
|
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.SavedProject).WithMany(x => x.SharedLayers).HasForeignKey(x => x.SavedProjectId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
}
|
|
}
|