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,249 @@
|
||||
-- =====================================================================
|
||||
-- CONTENT SCHEMA — Part 5: CMS (blogs, comments, slides, routes, settings)
|
||||
-- =====================================================================
|
||||
|
||||
SET search_path TO content, public;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- blogs / landings
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TYPE blog_kind AS ENUM ('Blog','Landing');
|
||||
|
||||
CREATE TABLE blogs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID, -- NULL = global
|
||||
kind blog_kind NOT NULL DEFAULT 'Blog',
|
||||
|
||||
slug CITEXT UNIQUE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
short_description TEXT,
|
||||
content TEXT NOT NULL,
|
||||
|
||||
-- SEO
|
||||
meta_title TEXT,
|
||||
meta_description TEXT,
|
||||
meta_keywords TEXT,
|
||||
include_in_site_map BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
-- Media
|
||||
image TEXT,
|
||||
cover TEXT,
|
||||
|
||||
-- Author
|
||||
author_user_id UUID, -- references identity.users (loose)
|
||||
author_display_name TEXT,
|
||||
|
||||
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
publish_date TIMESTAMPTZ,
|
||||
|
||||
view_count BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_blogs_published ON blogs(is_published, publish_date DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_blogs_tenant ON blogs(tenant_id) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE TRIGGER tg_blogs_updated_at
|
||||
BEFORE UPDATE ON blogs FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- comments — on blogs or project containers
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE comments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
user_id UUID NOT NULL, -- references identity.users (loose)
|
||||
|
||||
blog_id UUID REFERENCES blogs(id) ON DELETE CASCADE,
|
||||
container_id UUID REFERENCES project_containers(id) ON DELETE CASCADE,
|
||||
parent_comment_id UUID REFERENCES comments(id) ON DELETE CASCADE,
|
||||
|
||||
content TEXT NOT NULL,
|
||||
rate NUMERIC(3,2), -- 0-5
|
||||
|
||||
is_approved BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
|
||||
CHECK (
|
||||
(blog_id IS NOT NULL)::INT + (container_id IS NOT NULL)::INT = 1
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_comments_blog ON comments(blog_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_comments_container ON comments(container_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_comments_user ON comments(user_id);
|
||||
CREATE INDEX idx_comments_pending ON comments(is_approved) WHERE is_approved = FALSE AND deleted_at IS NULL;
|
||||
|
||||
CREATE TRIGGER tg_comments_updated_at
|
||||
BEFORE UPDATE ON comments FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- home_page_events — marketing banners / events
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE home_page_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
title TEXT,
|
||||
subtitle TEXT,
|
||||
description TEXT,
|
||||
badge TEXT,
|
||||
badge_class TEXT,
|
||||
button_text TEXT,
|
||||
button_url TEXT,
|
||||
button_class TEXT,
|
||||
color TEXT,
|
||||
background_color TEXT,
|
||||
text_color TEXT,
|
||||
image TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort INT NOT NULL DEFAULT 0,
|
||||
starts_at TIMESTAMPTZ,
|
||||
ends_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_home_events_active ON home_page_events(tenant_id, is_active);
|
||||
|
||||
CREATE TRIGGER tg_home_events_updated_at
|
||||
BEFORE UPDATE ON home_page_events FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- new_slides — carousel
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TYPE slide_type AS ENUM ('Hero','Promo','Tutorial','Category','Custom');
|
||||
|
||||
CREATE TABLE new_slides (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
keyword TEXT,
|
||||
title TEXT,
|
||||
image TEXT,
|
||||
parameter TEXT,
|
||||
slide_type slide_type NOT NULL DEFAULT 'Hero',
|
||||
expire_date TIMESTAMPTZ,
|
||||
sort INT NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_slides_active ON new_slides(tenant_id, is_active);
|
||||
|
||||
CREATE TRIGGER tg_slides_updated_at
|
||||
BEFORE UPDATE ON new_slides FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- internal_routes / custom_routes — SEO + redirects
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE internal_routes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
name TEXT,
|
||||
image TEXT,
|
||||
slug CITEXT NOT NULL,
|
||||
priority INT NOT NULL DEFAULT 5,
|
||||
last_date TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE custom_routes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
target TEXT NOT NULL, -- source path
|
||||
destination TEXT NOT NULL, -- redirect target
|
||||
redirect_code INT NOT NULL DEFAULT 301,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_custom_routes_active ON custom_routes(tenant_id, is_active);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- website_settings — key-value config (per tenant)
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE website_settings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID, -- NULL = global default
|
||||
key TEXT NOT NULL,
|
||||
value JSONB NOT NULL,
|
||||
description TEXT,
|
||||
is_secret BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (tenant_id, key)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_settings_tenant ON website_settings(tenant_id);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- learn — help articles
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE learn (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
title TEXT NOT NULL,
|
||||
body TEXT,
|
||||
demo_url TEXT,
|
||||
mode TEXT,
|
||||
sort INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- trainings — video tutorials
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE trainings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
video_url TEXT,
|
||||
thumbnail_url TEXT,
|
||||
sort INT NOT NULL DEFAULT 0,
|
||||
is_published BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- favorite_folders — user collections of templates
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE favorite_folders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL, -- references identity.users
|
||||
tenant_id UUID NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_fav_folders_user ON favorite_folders(user_id);
|
||||
|
||||
CREATE TRIGGER tg_fav_folders_updated_at
|
||||
BEFORE UPDATE ON favorite_folders FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- favorite_containers — saved template references in user's folders
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE favorite_containers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL,
|
||||
tenant_id UUID NOT NULL,
|
||||
container_id UUID NOT NULL REFERENCES project_containers(id) ON DELETE CASCADE,
|
||||
folder_id UUID REFERENCES favorite_folders(id) ON DELETE SET NULL,
|
||||
note TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (user_id, container_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_fav_containers_user ON favorite_containers(user_id);
|
||||
Reference in New Issue
Block a user