Files
gochat/backend/migrations/000012_add_knowledge_base_tables.up.sql
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

146 lines
6.4 KiB
SQL

-- GoChat Knowledge Base (Help Center) tables
-- Reference: Chatwoot Portal/Category/Article/Folder models
-- M4 milestone: KnowledgeBase + Article CRUD + Search
-- Portals (Knowledge Base containers)
-- Chatwoot names this "Portal" in the data model; concept = "Knowledge Base"
CREATE TABLE IF NOT EXISTS portals (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT DEFAULT '',
logo_url VARCHAR(512) DEFAULT '',
header_text TEXT DEFAULT '',
homepage_link VARCHAR(512) DEFAULT '',
page_title VARCHAR(255) DEFAULT '',
color VARCHAR(50) DEFAULT '#1f93ff',
custom_domain VARCHAR(255) DEFAULT '',
locale VARCHAR(10) DEFAULT 'en',
archived BOOLEAN DEFAULT FALSE,
portal_configuration JSONB DEFAULT '{}',
ssl_settings JSONB DEFAULT '{}',
homepage_content TEXT DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX idx_portals_slug ON portals(slug) WHERE deleted_at IS NULL;
CREATE INDEX idx_portals_account_id ON portals(account_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_portals_custom_domain ON portals(custom_domain) WHERE deleted_at IS NULL AND custom_domain != '';
CREATE INDEX idx_portals_archived ON portals(archived) WHERE deleted_at IS NULL;
CREATE INDEX idx_portals_deleted_at ON portals(deleted_at);
-- Categories (article grouping within a portal)
CREATE TABLE IF NOT EXISTS categories (
id SERIAL PRIMARY KEY,
portal_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT DEFAULT '',
slug VARCHAR(255) NOT NULL,
icon VARCHAR(255) DEFAULT '',
position INTEGER DEFAULT 0,
locale VARCHAR(10) DEFAULT 'en',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX idx_categories_slug ON categories(slug) WHERE deleted_at IS NULL;
CREATE INDEX idx_categories_portal_id ON categories(portal_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_categories_position ON categories(portal_id, position) WHERE deleted_at IS NULL;
CREATE INDEX idx_categories_deleted_at ON categories(deleted_at);
-- Related categories (many-to-many between categories)
CREATE TABLE IF NOT EXISTS related_categories (
id SERIAL PRIMARY KEY,
category_id INTEGER NOT NULL,
related_category_id INTEGER NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_related_categories_category_id ON related_categories(category_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_related_categories_related_category_id ON related_categories(related_category_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_related_categories_deleted_at ON related_categories(deleted_at);
-- Articles (help center content)
CREATE TABLE IF NOT EXISTS articles (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL,
portal_id INTEGER NOT NULL,
category_id INTEGER,
folder_id INTEGER,
author_id INTEGER,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT DEFAULT '',
content TEXT DEFAULT '',
status VARCHAR(50) NOT NULL DEFAULT 'draft',
position INTEGER DEFAULT 0,
views INTEGER DEFAULT 0,
locale VARCHAR(10) DEFAULT 'en',
associated_article_id INTEGER,
meta JSONB DEFAULT '{}',
custom_attributes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX idx_articles_slug ON articles(slug) WHERE deleted_at IS NULL;
CREATE INDEX idx_articles_account_id ON articles(account_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_articles_portal_id ON articles(portal_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_articles_category_id ON articles(category_id) WHERE deleted_at IS NULL AND category_id IS NOT NULL;
CREATE INDEX idx_articles_folder_id ON articles(folder_id) WHERE deleted_at IS NULL AND folder_id IS NOT NULL;
CREATE INDEX idx_articles_author_id ON articles(author_id) WHERE deleted_at IS NULL AND author_id IS NOT NULL;
CREATE INDEX idx_articles_status ON articles(portal_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_articles_position ON articles(portal_id, position) WHERE deleted_at IS NULL;
CREATE INDEX idx_articles_associated_article_id ON articles(associated_article_id) WHERE deleted_at IS NULL AND associated_article_id IS NOT NULL;
CREATE INDEX idx_articles_deleted_at ON articles(deleted_at);
-- Folders (article organization within a portal)
CREATE TABLE IF NOT EXISTS folders (
id SERIAL PRIMARY KEY,
portal_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_folders_portal_id ON folders(portal_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_folders_deleted_at ON folders(deleted_at);
-- Portal members (user membership with roles)
CREATE TABLE IF NOT EXISTS portal_members (
id SERIAL PRIMARY KEY,
portal_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'reader',
custom_attributes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_portal_members_portal_id ON portal_members(portal_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_portal_members_user_id ON portal_members(user_id) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_portal_members_portal_user ON portal_members(portal_id, user_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_portal_members_deleted_at ON portal_members(deleted_at);
-- Article embeddings (vector search, enterprise feature)
CREATE TABLE IF NOT EXISTS article_embeddings (
id SERIAL PRIMARY KEY,
article_id INTEGER NOT NULL,
embedding JSONB NOT NULL,
term TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_article_embeddings_article_id ON article_embeddings(article_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_article_embeddings_deleted_at ON article_embeddings(deleted_at);