Initial miaojingAI project with image resolution guard
This commit is contained in:
20
scripts/apply-database-patch.sh
Normal file
20
scripts/apply-database-patch.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
|
||||
if [ -f "${COZE_WORKSPACE_PATH}/.env.local" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${COZE_WORKSPACE_PATH}/.env.local"
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
if [ -z "${LOCAL_DB_URL:-}" ]; then
|
||||
echo "LOCAL_DB_URL is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
psql "${LOCAL_DB_URL}" -v ON_ERROR_STOP=1 -f "${COZE_WORKSPACE_PATH}/scripts/database-optimization-patch.sql"
|
||||
72
scripts/backup-create.sh
Normal file
72
scripts/backup-create.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
REQUESTED_BACKUP_DIR="${BACKUP_DIR:-}"
|
||||
REQUESTED_LOCAL_DB_URL="${LOCAL_DB_URL:-}"
|
||||
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
if [ -f ".env.local" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source ".env.local"
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
[ -n "${REQUESTED_LOCAL_DB_URL}" ] && LOCAL_DB_URL="${REQUESTED_LOCAL_DB_URL}"
|
||||
BACKUP_DIR="${REQUESTED_BACKUP_DIR:-${BACKUP_DIR:-${COZE_WORKSPACE_PATH}/backups}}"
|
||||
BACKUP_FILE="${BACKUP_DIR}/miaojing-backup-${TIMESTAMP}.tar.gz"
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
chmod 700 "${BACKUP_DIR}"
|
||||
|
||||
if [ -z "${LOCAL_DB_URL:-}" ]; then
|
||||
echo "LOCAL_DB_URL is required in .env.local or environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v pg_dump >/dev/null 2>&1 || {
|
||||
echo "pg_dump is required to create backups." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
pg_dump "${LOCAL_DB_URL}" --format=custom --file "${TMP_DIR}/database.dump"
|
||||
|
||||
STORAGE_SOURCE="${LOCAL_STORAGE_DIR:-${COZE_WORKSPACE_PATH}/local-storage}"
|
||||
if [ -d "${STORAGE_SOURCE}" ]; then
|
||||
cp -a "${STORAGE_SOURCE}" "${TMP_DIR}/local-storage"
|
||||
fi
|
||||
|
||||
if [ -f ".env.local" ]; then
|
||||
cp ".env.local" "${TMP_DIR}/.env.local"
|
||||
fi
|
||||
|
||||
if [ -f "package.json" ]; then
|
||||
cp "package.json" "${TMP_DIR}/package.json"
|
||||
fi
|
||||
|
||||
cat > "${TMP_DIR}/manifest.json" <<EOF
|
||||
{
|
||||
"app": "miaojingAI",
|
||||
"createdAt": "$(date -Iseconds)",
|
||||
"hostname": "$(hostname)",
|
||||
"includes": ["database.dump", "local-storage", ".env.local", "package.json"]
|
||||
}
|
||||
EOF
|
||||
|
||||
tar -czf "${BACKUP_FILE}" -C "${TMP_DIR}" .
|
||||
chmod 600 "${BACKUP_FILE}"
|
||||
|
||||
find "${BACKUP_DIR}" -maxdepth 1 -name 'miaojing-backup-*.tar.gz' -type f \
|
||||
-printf '%T@ %p\n' | sort -rn | awk 'NR>10 {print $2}' | xargs -r rm -f
|
||||
|
||||
echo "${BACKUP_FILE}"
|
||||
32
scripts/backup-list.sh
Normal file
32
scripts/backup-list.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
REQUESTED_BACKUP_DIR="${BACKUP_DIR:-}"
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
if [ -f ".env.local" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source ".env.local"
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
BACKUP_DIR="${REQUESTED_BACKUP_DIR:-${BACKUP_DIR:-${COZE_WORKSPACE_PATH}/backups}}"
|
||||
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
chmod 700 "${BACKUP_DIR}"
|
||||
|
||||
if ! compgen -G "${BACKUP_DIR}/miaojing-backup-*.tar.gz" >/dev/null; then
|
||||
echo "No backups found in ${BACKUP_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '%-40s %-12s %s\n' "FILE" "SIZE" "MODIFIED"
|
||||
find "${BACKUP_DIR}" -maxdepth 1 -name 'miaojing-backup-*.tar.gz' -type f \
|
||||
-printf '%T@ %f %s %TY-%Tm-%Td %TH:%TM\n' \
|
||||
| sort -rn \
|
||||
| awk '{printf "%-40s %-12s %s %s\n", $2, $3, $4, $5}'
|
||||
65
scripts/backup-restore.sh
Normal file
65
scripts/backup-restore.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
BACKUP_FILE="${1:-}"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ -z "${BACKUP_FILE}" ]; then
|
||||
echo "Usage: pnpm backup:restore <backup-file.tar.gz>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ ! -f "${BACKUP_FILE}" ]; then
|
||||
echo "Backup file not found: ${BACKUP_FILE}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
if [ -f ".env.local" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source ".env.local"
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
if [ -z "${LOCAL_DB_URL:-}" ]; then
|
||||
echo "LOCAL_DB_URL is required in .env.local or environment." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v pg_restore >/dev/null 2>&1 || {
|
||||
echo "pg_restore is required to restore backups." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
tar -xzf "${BACKUP_FILE}" -C "${TMP_DIR}"
|
||||
|
||||
if [ ! -f "${TMP_DIR}/database.dump" ]; then
|
||||
echo "Invalid backup: missing database.dump." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
pg_restore --clean --if-exists --no-owner --dbname "${LOCAL_DB_URL}" "${TMP_DIR}/database.dump"
|
||||
|
||||
if [ -d "${TMP_DIR}/local-storage" ]; then
|
||||
STORAGE_TARGET="${LOCAL_STORAGE_DIR:-${COZE_WORKSPACE_PATH}/local-storage}"
|
||||
rm -rf "${STORAGE_TARGET}"
|
||||
mkdir -p "$(dirname "${STORAGE_TARGET}")"
|
||||
cp -a "${TMP_DIR}/local-storage" "${STORAGE_TARGET}"
|
||||
fi
|
||||
|
||||
if [ -f "${TMP_DIR}/.env.local" ]; then
|
||||
cp "${TMP_DIR}/.env.local" ".env.local"
|
||||
chmod 600 ".env.local"
|
||||
fi
|
||||
|
||||
echo "Restore completed from ${BACKUP_FILE}"
|
||||
21
scripts/build.sh
Normal file
21
scripts/build.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
if [ "${INSTALL_DEPS:-0}" = "1" ] || [ ! -d node_modules ]; then
|
||||
echo "Installing dependencies..."
|
||||
pnpm install --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
|
||||
else
|
||||
echo "Skipping dependency install. Set INSTALL_DEPS=1 to force it."
|
||||
fi
|
||||
|
||||
echo "Building the Next.js project..."
|
||||
pnpm next build
|
||||
|
||||
echo "Bundling server with tsup..."
|
||||
pnpm tsup src/server.ts --format cjs --platform node --target node20 --outDir dist --no-splitting --no-minify
|
||||
|
||||
echo "Build completed successfully!"
|
||||
50
scripts/check-boundaries.sh
Normal file
50
scripts/check-boundaries.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
fail=0
|
||||
|
||||
search_pattern() {
|
||||
local pattern="$1"
|
||||
shift
|
||||
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
rg -n "$pattern" "$@" || true
|
||||
else
|
||||
grep -RInE "$pattern" "$@" || true
|
||||
fi
|
||||
}
|
||||
|
||||
check_no_match() {
|
||||
local label="$1"
|
||||
local pattern="$2"
|
||||
shift 2
|
||||
local output
|
||||
|
||||
output="$(search_pattern "$pattern" "$@")"
|
||||
if [ -n "$output" ]; then
|
||||
echo "Boundary violation: ${label}" >&2
|
||||
echo "$output" >&2
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
check_no_match \
|
||||
"web module must not import server database/storage internals" \
|
||||
"@/storage|@/lib/local-storage|@/lib/session-auth|@/lib/admin-auth|@/lib/runtime-env|@/lib/server-crypto" \
|
||||
src/modules/web
|
||||
|
||||
check_no_match \
|
||||
"console module must not import server database/storage internals directly" \
|
||||
"@/storage|@/lib/local-storage|@/lib/runtime-env|@/lib/server-crypto" \
|
||||
src/modules/console
|
||||
|
||||
check_no_match \
|
||||
"shared module must not depend on app-specific modules" \
|
||||
"@/modules/(web|console|api)|@/app/|@/components/admin" \
|
||||
src/modules/shared
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Module boundaries OK"
|
||||
169
scripts/database-optimization-patch.sql
Normal file
169
scripts/database-optimization-patch.sql
Normal file
@@ -0,0 +1,169 @@
|
||||
-- Idempotent local PostgreSQL patch for production maintenance.
|
||||
-- It creates missing application tables and adds indexes used by hot paths.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS works (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID,
|
||||
title VARCHAR(255),
|
||||
type VARCHAR(32) NOT NULL,
|
||||
prompt TEXT,
|
||||
negative_prompt TEXT,
|
||||
params JSONB DEFAULT '{}'::jsonb,
|
||||
result_url TEXT,
|
||||
thumbnail_url TEXT,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
duration NUMERIC(6, 2),
|
||||
is_public BOOLEAN NOT NULL DEFAULT false,
|
||||
likes_count INTEGER NOT NULL DEFAULT 0,
|
||||
credits_cost INTEGER NOT NULL DEFAULT 0,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'completed',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID,
|
||||
order_no VARCHAR(64) NOT NULL UNIQUE,
|
||||
product_type VARCHAR(32) NOT NULL,
|
||||
product_name VARCHAR(255) NOT NULL,
|
||||
amount NUMERIC(10, 2) NOT NULL,
|
||||
credits_amount INTEGER,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
payment_method VARCHAR(32),
|
||||
paid_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_api_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID,
|
||||
provider VARCHAR(64) NOT NULL,
|
||||
api_url TEXT,
|
||||
model_name VARCHAR(128),
|
||||
api_key_encrypted TEXT NOT NULL,
|
||||
api_key_preview VARCHAR(20),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS work_likes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID,
|
||||
work_id UUID NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(16) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'queued',
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
result JSONB,
|
||||
error TEXT,
|
||||
user_id UUID,
|
||||
provider VARCHAR(128),
|
||||
model_name VARCHAR(255),
|
||||
api_url TEXT,
|
||||
progress JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
started_at TIMESTAMPTZ,
|
||||
finished_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS user_id UUID;
|
||||
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS provider VARCHAR(128);
|
||||
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS model_name VARCHAR(255);
|
||||
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS api_url TEXT;
|
||||
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS progress JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS works_user_created_idx ON works (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS works_public_status_created_idx ON works (is_public, status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS works_public_status_likes_idx ON works (is_public, status, likes_count DESC);
|
||||
CREATE INDEX IF NOT EXISTS works_type_created_idx ON works (type, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS works_status_created_idx ON works (status, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS credit_transactions_user_created_idx ON credit_transactions (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS credit_transactions_type_created_idx ON credit_transactions (type, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS orders_user_created_idx ON orders (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS orders_status_created_idx ON orders (status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS orders_order_no_idx ON orders (order_no);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS user_api_keys_user_active_idx ON user_api_keys (user_id, is_active);
|
||||
CREATE INDEX IF NOT EXISTS user_api_keys_provider_idx ON user_api_keys (provider);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS work_likes_user_id_idx ON work_likes (user_id);
|
||||
CREATE INDEX IF NOT EXISTS work_likes_work_id_idx ON work_likes (work_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS work_likes_user_work_uniq ON work_likes (user_id, work_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS announcements_active_window_idx ON announcements (is_active, starts_at, expires_at);
|
||||
CREATE INDEX IF NOT EXISTS profiles_email_trgm_idx ON profiles USING GIN (LOWER(email) gin_trgm_ops);
|
||||
CREATE INDEX IF NOT EXISTS profiles_nickname_trgm_idx ON profiles USING GIN (LOWER(COALESCE(nickname, '')) gin_trgm_ops);
|
||||
CREATE INDEX IF NOT EXISTS profiles_phone_trgm_idx ON profiles USING GIN (LOWER(COALESCE(phone, '')) gin_trgm_ops);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_status_created_idx ON generation_jobs (status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_status_updated_idx ON generation_jobs (status, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_running_timeout_idx ON generation_jobs (updated_at) WHERE status = 'running';
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_created_idx ON generation_jobs (created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_user_created_idx ON generation_jobs (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_provider_model_created_idx ON generation_jobs (type, provider, model_name, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform_log_settings (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
retention_days INTEGER NOT NULL DEFAULT 30,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
INSERT INTO platform_log_settings (id, retention_days)
|
||||
VALUES (1, 30)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(32) NOT NULL,
|
||||
level VARCHAR(16) NOT NULL DEFAULT 'info',
|
||||
action VARCHAR(128) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
user_id UUID,
|
||||
user_name VARCHAR(255),
|
||||
user_email VARCHAR(255),
|
||||
target_type VARCHAR(64),
|
||||
target_id VARCHAR(255),
|
||||
ip_address VARCHAR(64),
|
||||
user_agent TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_type_created_idx ON platform_logs (type, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_level_created_idx ON platform_logs (level, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_created_idx ON platform_logs (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_created_idx ON platform_logs (created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_name_idx ON platform_logs (LOWER(COALESCE(user_name, '')));
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_email_idx ON platform_logs (LOWER(COALESCE(user_email, '')));
|
||||
|
||||
DROP POLICY IF EXISTS "site_config_write_auth" ON site_config;
|
||||
DROP POLICY IF EXISTS "announcements_write_auth" ON announcements;
|
||||
DROP POLICY IF EXISTS "site_stats_write_auth" ON site_stats;
|
||||
|
||||
DROP POLICY IF EXISTS "site_config_admin_write" ON site_config;
|
||||
DROP POLICY IF EXISTS "announcements_admin_write" ON announcements;
|
||||
|
||||
CREATE POLICY "site_config_admin_write" ON site_config FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
CREATE POLICY "announcements_admin_write" ON announcements FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
ANALYZE;
|
||||
1156
scripts/deploy-or-upgrade.sh
Normal file
1156
scripts/deploy-or-upgrade.sh
Normal file
File diff suppressed because it is too large
Load Diff
34
scripts/dev.sh
Normal file
34
scripts/dev.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
|
||||
PORT=${1:-5000}
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
DEPLOY_RUN_PORT=$PORT
|
||||
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
kill_port_if_listening() {
|
||||
local pids
|
||||
pids=$(ss -H -lntp 2>/dev/null | awk -v port="${DEPLOY_RUN_PORT}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | paste -sd' ' - || true)
|
||||
if [[ -z "${pids}" ]]; then
|
||||
echo "Port ${DEPLOY_RUN_PORT} is free."
|
||||
return
|
||||
fi
|
||||
echo "Port ${DEPLOY_RUN_PORT} in use by PIDs: ${pids} (SIGKILL)"
|
||||
echo "${pids}" | xargs -I {} kill -9 {}
|
||||
sleep 1
|
||||
pids=$(ss -H -lntp 2>/dev/null | awk -v port="${DEPLOY_RUN_PORT}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | paste -sd' ' - || true)
|
||||
if [[ -n "${pids}" ]]; then
|
||||
echo "Warning: port ${DEPLOY_RUN_PORT} still busy after SIGKILL, PIDs: ${pids}"
|
||||
else
|
||||
echo "Port ${DEPLOY_RUN_PORT} cleared."
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Clearing port ${PORT} before start."
|
||||
kill_port_if_listening
|
||||
echo "Starting HTTP service on port ${PORT} for dev..."
|
||||
|
||||
PORT=$PORT pnpm tsx watch src/server.ts
|
||||
663
scripts/init-database.sql
Normal file
663
scripts/init-database.sql
Normal file
@@ -0,0 +1,663 @@
|
||||
-- ============================================================
|
||||
-- 妙境 AI 创作平台 — 数据库初始化脚本
|
||||
-- 适用于: PostgreSQL 14+ (Supabase / 自托管)
|
||||
-- 执行方式: 在 Supabase SQL Editor 或 psql 中运行
|
||||
-- ============================================================
|
||||
|
||||
-- 0. 启用必要扩展
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- 1. 创建 auth 模式和 users 表
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_proc p
|
||||
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'auth' AND p.proname = 'uid'
|
||||
) THEN
|
||||
EXECUTE 'CREATE FUNCTION auth.uid() RETURNS UUID AS $fn$ SELECT NULLIF(current_setting(''request.jwt.claim.sub'', true), '''')::UUID; $fn$ LANGUAGE SQL STABLE';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_proc p
|
||||
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
WHERE n.nspname = 'auth' AND p.proname = 'role'
|
||||
) THEN
|
||||
EXECUTE 'CREATE FUNCTION auth.role() RETURNS TEXT AS $fn$ SELECT COALESCE(NULLIF(current_setting(''request.jwt.claim.role'', true), ''''), ''anon''); $fn$ LANGUAGE SQL STABLE';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth.users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) UNIQUE,
|
||||
password_hash TEXT,
|
||||
raw_user_meta_data JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS auth_users_email_idx ON auth.users (email);
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 用户资料表 (profiles)
|
||||
-- 与 Supabase Auth 的 auth.users 表关联
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
nickname VARCHAR(128),
|
||||
avatar_url TEXT,
|
||||
phone VARCHAR(20),
|
||||
role VARCHAR(32) NOT NULL DEFAULT 'user', -- guest, user, vip, enterprise_admin, enterprise_member, admin
|
||||
membership_tier VARCHAR(32) NOT NULL DEFAULT 'free', -- free, basic, pro, enterprise
|
||||
membership_expires_at TIMESTAMPTZ,
|
||||
credits_balance INTEGER NOT NULL DEFAULT 0,
|
||||
daily_quota_used INTEGER NOT NULL DEFAULT 0,
|
||||
daily_quota_limit INTEGER NOT NULL DEFAULT 5,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
email_verified BOOLEAN NOT NULL DEFAULT false,
|
||||
email_verified_at TIMESTAMPTZ,
|
||||
email_bound_at TIMESTAMPTZ,
|
||||
email_sender_domain VARCHAR(255),
|
||||
preferred_theme VARCHAR(16) NOT NULL DEFAULT 'dark',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS profiles_email_idx ON profiles (email);
|
||||
CREATE INDEX IF NOT EXISTS profiles_role_idx ON profiles (role);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 创作作品表 (works)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS works (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID DEFAULT auth.uid(),
|
||||
title VARCHAR(255),
|
||||
type VARCHAR(32) NOT NULL, -- text2img, img2img, text2video, img2video
|
||||
prompt TEXT,
|
||||
negative_prompt TEXT,
|
||||
params JSONB, -- 生成参数 (画面比例、分辨率、模型等)
|
||||
result_url TEXT, -- 生成文件的 URL
|
||||
thumbnail_url TEXT,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
duration NUMERIC(6, 2), -- 视频时长 (秒)
|
||||
is_public BOOLEAN NOT NULL DEFAULT false,
|
||||
likes_count INTEGER NOT NULL DEFAULT 0,
|
||||
views_count INTEGER NOT NULL DEFAULT 0,
|
||||
credits_cost INTEGER NOT NULL DEFAULT 0,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'completed', -- pending, processing, completed, failed
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS works_user_id_idx ON works (user_id);
|
||||
CREATE INDEX IF NOT EXISTS works_type_idx ON works (type);
|
||||
CREATE INDEX IF NOT EXISTS works_is_public_idx ON works (is_public);
|
||||
CREATE INDEX IF NOT EXISTS works_created_at_idx ON works (created_at);
|
||||
CREATE INDEX IF NOT EXISTS works_status_idx ON works (status);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 积分记录表 (credit_transactions)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS credit_transactions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID DEFAULT auth.uid(),
|
||||
amount INTEGER NOT NULL, -- 正数=入账, 负数=消费
|
||||
balance_after INTEGER NOT NULL,
|
||||
type VARCHAR(32) NOT NULL, -- purchase, consume, gift, reward, refund
|
||||
description VARCHAR(500),
|
||||
related_work_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS credit_transactions_user_id_idx ON credit_transactions (user_id);
|
||||
CREATE INDEX IF NOT EXISTS credit_transactions_type_idx ON credit_transactions (type);
|
||||
CREATE INDEX IF NOT EXISTS credit_transactions_created_at_idx ON credit_transactions (created_at);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. 订单表 (orders)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID DEFAULT auth.uid(),
|
||||
order_no VARCHAR(64) NOT NULL UNIQUE,
|
||||
product_type VARCHAR(32) NOT NULL, -- membership, credits, api
|
||||
product_name VARCHAR(255) NOT NULL,
|
||||
amount NUMERIC(10, 2) NOT NULL,
|
||||
credits_amount INTEGER, -- 购买的积分数
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending', -- pending, paid, cancelled, refunded
|
||||
payment_method VARCHAR(32), -- wechat, alipay, stripe
|
||||
paid_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS orders_user_id_idx ON orders (user_id);
|
||||
CREATE INDEX IF NOT EXISTS orders_order_no_idx ON orders (order_no);
|
||||
CREATE INDEX IF NOT EXISTS orders_status_idx ON orders (status);
|
||||
CREATE INDEX IF NOT EXISTS orders_created_at_idx ON orders (created_at);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. 生成任务队列表 (generation_jobs)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(16) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'queued',
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
result JSONB,
|
||||
error TEXT,
|
||||
user_id UUID,
|
||||
provider VARCHAR(128),
|
||||
model_name VARCHAR(255),
|
||||
api_url TEXT,
|
||||
progress JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
started_at TIMESTAMPTZ,
|
||||
finished_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_status_created_idx ON generation_jobs (status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_status_updated_idx ON generation_jobs (status, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_running_timeout_idx ON generation_jobs (updated_at) WHERE status = 'running';
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_created_idx ON generation_jobs (created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_user_created_idx ON generation_jobs (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_provider_model_created_idx ON generation_jobs (type, provider, model_name, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 6. 用户自定义 API 密钥表 (user_api_keys)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS user_api_keys (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID DEFAULT auth.uid(),
|
||||
provider VARCHAR(64) NOT NULL, -- openai, stabilityai, runway, etc.
|
||||
api_url TEXT, -- 完整 API 端点 URL
|
||||
model_name VARCHAR(128), -- 具体模型名称
|
||||
api_key_encrypted TEXT NOT NULL, -- 加密存储的 API Key
|
||||
api_key_preview VARCHAR(20), -- Key 尾号 (如 sk-...4f3e)
|
||||
supplier_name VARCHAR(128),
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'image',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS user_api_keys_user_id_idx ON user_api_keys (user_id);
|
||||
CREATE INDEX IF NOT EXISTS user_api_keys_provider_idx ON user_api_keys (provider);
|
||||
|
||||
-- ============================================================
|
||||
-- 7. 作品点赞表 (work_likes)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS work_likes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID DEFAULT auth.uid(),
|
||||
work_id UUID NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS work_likes_user_id_idx ON work_likes (user_id);
|
||||
CREATE INDEX IF NOT EXISTS work_likes_work_id_idx ON work_likes (work_id);
|
||||
|
||||
-- 唯一约束:每个用户对每个作品只能点赞一次
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS work_likes_user_work_uniq ON work_likes (user_id, work_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 8. 网站配置表 (site_config)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS site_config (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
site_name VARCHAR(128) NOT NULL DEFAULT '妙境',
|
||||
site_tab_title VARCHAR(255) NOT NULL DEFAULT '妙境 - AI创作平台',
|
||||
site_description TEXT NOT NULL DEFAULT '',
|
||||
site_keywords TEXT NOT NULL DEFAULT '',
|
||||
logo_url TEXT,
|
||||
favicon_url TEXT,
|
||||
announcement TEXT NOT NULL DEFAULT '',
|
||||
membership_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
terms_of_service TEXT NOT NULL DEFAULT '',
|
||||
privacy_policy TEXT NOT NULL DEFAULT '',
|
||||
about_us TEXT NOT NULL DEFAULT '',
|
||||
help_center TEXT NOT NULL DEFAULT '',
|
||||
filing_info TEXT NOT NULL DEFAULT '',
|
||||
filing_url TEXT NOT NULL DEFAULT '',
|
||||
public_security_filing_info TEXT NOT NULL DEFAULT '',
|
||||
public_security_filing_url TEXT NOT NULL DEFAULT '',
|
||||
log_retention_days INTEGER NOT NULL DEFAULT 30,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- 插入默认配置
|
||||
INSERT INTO site_config (id, site_name, site_tab_title)
|
||||
VALUES (1, '妙境', '妙境 - AI创作平台')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. 公告表 (announcements)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS announcements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL, -- 支持 Markdown
|
||||
type VARCHAR(32) NOT NULL DEFAULT 'site',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
starts_at TIMESTAMPTZ,
|
||||
expires_at TIMESTAMPTZ,
|
||||
created_by UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS announcements_is_active_idx ON announcements (is_active);
|
||||
CREATE INDEX IF NOT EXISTS announcements_expires_at_idx ON announcements (expires_at);
|
||||
|
||||
-- ============================================================
|
||||
-- 10. 网站统计表 (site_stats)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS site_stats (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
total_visits BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO site_stats (id, total_visits) VALUES (1, 0) ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 11. 平台日志
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS platform_log_settings (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
retention_days INTEGER NOT NULL DEFAULT 30,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO platform_log_settings (id, retention_days)
|
||||
VALUES (1, 30)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(32) NOT NULL,
|
||||
level VARCHAR(16) NOT NULL DEFAULT 'info',
|
||||
action VARCHAR(128) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
user_id UUID,
|
||||
user_name VARCHAR(255),
|
||||
user_email VARCHAR(255),
|
||||
target_type VARCHAR(64),
|
||||
target_id VARCHAR(255),
|
||||
ip_address VARCHAR(64),
|
||||
user_agent TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_type_created_idx ON platform_logs (type, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_level_created_idx ON platform_logs (level, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_created_idx ON platform_logs (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_created_idx ON platform_logs (created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 12. API 供应商与推荐模型配置
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS api_providers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(128) NOT NULL UNIQUE,
|
||||
default_api_url TEXT,
|
||||
default_model VARCHAR(255),
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'image',
|
||||
website TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS model_recommendations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(255),
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'image',
|
||||
provider_id UUID REFERENCES api_providers(id) ON DELETE SET NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS api_providers_active_sort_idx ON api_providers (is_active, sort_order);
|
||||
CREATE INDEX IF NOT EXISTS model_recommendations_active_type_sort_idx ON model_recommendations (is_active, type, sort_order);
|
||||
CREATE INDEX IF NOT EXISTS model_recommendations_provider_idx ON model_recommendations (provider_id);
|
||||
|
||||
INSERT INTO api_providers (name, default_api_url, default_model, type, website, is_active, sort_order)
|
||||
VALUES
|
||||
('硅基流动', 'https://api.siliconflow.cn/v1/images/generations', 'black-forest-labs/FLUX.1-schnell', 'image', 'https://cloud.siliconflow.cn', true, 10),
|
||||
('mozheAPI', 'https://openai.mozhevip.top', '', 'image', 'https://openai.mozhevip.top', true, 20),
|
||||
('OpenAI', 'https://api.openai.com/v1/images/generations', 'dall-e-3', 'image', NULL, true, 30),
|
||||
('Stability AI', 'https://api.stability.ai/v1/generation/stable-diffusion-xl/text-to-image', 'stable-diffusion-xl', 'image', NULL, true, 40),
|
||||
('Midjourney', '', 'midjourney-v6', 'image', NULL, true, 50),
|
||||
('Runway', 'https://api.runwayml.com/v1/image_to_video', 'gen-3-alpha', 'video', NULL, true, 60),
|
||||
('Pika', '', 'pika-1.0', 'video', NULL, true, 70),
|
||||
('Kling', '', 'kling-v1', 'video', NULL, true, 80),
|
||||
('DeepSeek', 'https://api.deepseek.com/v1/chat/completions', 'deepseek-chat', 'text', NULL, true, 90),
|
||||
('OpenAI GPT', 'https://api.openai.com/v1/chat/completions', 'gpt-4o', 'text', NULL, true, 100),
|
||||
('自定义', '', '', 'image', NULL, true, 999)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
INSERT INTO model_recommendations (model_name, display_name, type, provider_id, is_active, sort_order)
|
||||
SELECT 'gpt-image-2', 'gpt-image-2', 'image', NULL, true, 10
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM model_recommendations
|
||||
WHERE model_name = 'gpt-image-2' AND type = 'image' AND provider_id IS NULL
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 兼容旧版本库结构的幂等补丁
|
||||
-- ============================================================
|
||||
ALTER TABLE profiles
|
||||
ADD COLUMN IF NOT EXISTS email_verified BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS email_bound_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS email_sender_domain VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS preferred_theme VARCHAR(16) NOT NULL DEFAULT 'dark';
|
||||
|
||||
UPDATE profiles
|
||||
SET preferred_theme = 'dark'
|
||||
WHERE preferred_theme IS NULL
|
||||
OR preferred_theme NOT IN ('dark', 'light');
|
||||
|
||||
ALTER TABLE works
|
||||
ADD COLUMN IF NOT EXISTS views_count INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ;
|
||||
|
||||
ALTER TABLE user_api_keys
|
||||
ADD COLUMN IF NOT EXISTS supplier_name VARCHAR(128),
|
||||
ADD COLUMN IF NOT EXISTS note TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS type VARCHAR(16) NOT NULL DEFAULT 'image';
|
||||
|
||||
ALTER TABLE site_config
|
||||
ADD COLUMN IF NOT EXISTS site_description TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS site_keywords TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS announcement TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS membership_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ADD COLUMN IF NOT EXISTS terms_of_service TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS privacy_policy TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS about_us TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS help_center TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS filing_info TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS filing_url TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS public_security_filing_info TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS public_security_filing_url TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS log_retention_days INTEGER NOT NULL DEFAULT 30;
|
||||
|
||||
ALTER TABLE generation_jobs
|
||||
ADD COLUMN IF NOT EXISTS user_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS provider VARCHAR(128),
|
||||
ADD COLUMN IF NOT EXISTS model_name VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS api_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS progress JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_user_created_idx ON generation_jobs (user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS generation_jobs_provider_model_created_idx ON generation_jobs (type, provider, model_name, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_api_configs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
provider VARCHAR(128),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
api_url TEXT NOT NULL DEFAULT '',
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
api_key_encrypted TEXT NOT NULL DEFAULT '',
|
||||
api_key_preview VARCHAR(64) NOT NULL DEFAULT '',
|
||||
type VARCHAR(16) NOT NULL DEFAULT 'image',
|
||||
credits_per_use INTEGER NOT NULL DEFAULT 10,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS system_api_configs_active_type_sort_idx ON system_api_configs (is_active, type, sort_order);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payment_methods (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
public_config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
secret_config_encrypted JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
secret_config_preview JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
INSERT INTO payment_methods (id, type, name, is_active) VALUES
|
||||
('pm-alipay', 'alipay', '支付宝', true),
|
||||
('pm-wechat', 'wechat', '微信支付', false),
|
||||
('pm-manual', 'manual', '手动转账', false),
|
||||
('pm-stripe', 'stripe', 'Stripe', false)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_name_idx ON platform_logs (LOWER(COALESCE(user_name, '')));
|
||||
CREATE INDEX IF NOT EXISTS platform_logs_user_email_idx ON platform_logs (LOWER(COALESCE(user_email, '')));
|
||||
|
||||
ALTER TABLE announcements
|
||||
ADD COLUMN IF NOT EXISTS type VARCHAR(32) NOT NULL DEFAULT 'site';
|
||||
|
||||
ALTER TABLE platform_log_settings ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE platform_logs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE api_providers ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE model_recommendations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE system_api_configs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE payment_methods ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- ============================================================
|
||||
-- Row Level Security (RLS) 策略
|
||||
-- ============================================================
|
||||
|
||||
-- 启用所有表的 RLS
|
||||
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE works ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE credit_transactions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE user_api_keys ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE work_likes ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE site_config ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE announcements ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE site_stats ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS "profiles_read_own" ON profiles;
|
||||
DROP POLICY IF EXISTS "profiles_update_own" ON profiles;
|
||||
DROP POLICY IF EXISTS "profiles_admin_all" ON profiles;
|
||||
DROP POLICY IF EXISTS "works_read_public" ON works;
|
||||
DROP POLICY IF EXISTS "works_insert_own" ON works;
|
||||
DROP POLICY IF EXISTS "works_update_own" ON works;
|
||||
DROP POLICY IF EXISTS "works_delete_own" ON works;
|
||||
DROP POLICY IF EXISTS "works_admin_all" ON works;
|
||||
DROP POLICY IF EXISTS "credit_transactions_read_own" ON credit_transactions;
|
||||
DROP POLICY IF EXISTS "credit_transactions_admin_all" ON credit_transactions;
|
||||
DROP POLICY IF EXISTS "orders_read_own" ON orders;
|
||||
DROP POLICY IF EXISTS "orders_insert_own" ON orders;
|
||||
DROP POLICY IF EXISTS "orders_admin_all" ON orders;
|
||||
DROP POLICY IF EXISTS "user_api_keys_read_own" ON user_api_keys;
|
||||
DROP POLICY IF EXISTS "user_api_keys_insert_own" ON user_api_keys;
|
||||
DROP POLICY IF EXISTS "user_api_keys_update_own" ON user_api_keys;
|
||||
DROP POLICY IF EXISTS "user_api_keys_delete_own" ON user_api_keys;
|
||||
DROP POLICY IF EXISTS "work_likes_read_all" ON work_likes;
|
||||
DROP POLICY IF EXISTS "work_likes_insert_own" ON work_likes;
|
||||
DROP POLICY IF EXISTS "work_likes_delete_own" ON work_likes;
|
||||
DROP POLICY IF EXISTS "site_config_read_all" ON site_config;
|
||||
DROP POLICY IF EXISTS "site_config_write_auth" ON site_config;
|
||||
DROP POLICY IF EXISTS "site_config_admin_write" ON site_config;
|
||||
DROP POLICY IF EXISTS "announcements_read_all" ON announcements;
|
||||
DROP POLICY IF EXISTS "announcements_write_auth" ON announcements;
|
||||
DROP POLICY IF EXISTS "announcements_admin_write" ON announcements;
|
||||
DROP POLICY IF EXISTS "site_stats_read_all" ON site_stats;
|
||||
DROP POLICY IF EXISTS "site_stats_write_auth" ON site_stats;
|
||||
|
||||
-- profiles: 用户可读自己的资料,管理员可读写所有
|
||||
CREATE POLICY "profiles_read_own" ON profiles FOR SELECT USING (auth.uid() = id);
|
||||
CREATE POLICY "profiles_update_own" ON profiles FOR UPDATE USING (auth.uid() = id);
|
||||
CREATE POLICY "profiles_admin_all" ON profiles FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- works: 用户可管理自己的作品,公开作品所有人可读
|
||||
CREATE POLICY "works_read_public" ON works FOR SELECT USING (is_public = true OR auth.uid() = user_id);
|
||||
CREATE POLICY "works_insert_own" ON works FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
CREATE POLICY "works_update_own" ON works FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "works_delete_own" ON works FOR DELETE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "works_admin_all" ON works FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- credit_transactions: 用户可读自己的记录
|
||||
CREATE POLICY "credit_transactions_read_own" ON credit_transactions FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "credit_transactions_admin_all" ON credit_transactions FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- orders: 用户可读自己的订单
|
||||
CREATE POLICY "orders_read_own" ON orders FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "orders_insert_own" ON orders FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
CREATE POLICY "orders_admin_all" ON orders FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- user_api_keys: 用户可管理自己的密钥
|
||||
CREATE POLICY "user_api_keys_read_own" ON user_api_keys FOR SELECT USING (auth.uid() = user_id);
|
||||
CREATE POLICY "user_api_keys_insert_own" ON user_api_keys FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
CREATE POLICY "user_api_keys_update_own" ON user_api_keys FOR UPDATE USING (auth.uid() = user_id);
|
||||
CREATE POLICY "user_api_keys_delete_own" ON user_api_keys FOR DELETE USING (auth.uid() = user_id);
|
||||
|
||||
-- work_likes: 认证用户可点赞,所有人可读
|
||||
CREATE POLICY "work_likes_read_all" ON work_likes FOR SELECT USING (true);
|
||||
CREATE POLICY "work_likes_insert_own" ON work_likes FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||||
CREATE POLICY "work_likes_delete_own" ON work_likes FOR DELETE USING (auth.uid() = user_id);
|
||||
|
||||
-- site_config: 所有人可读,认证用户可写 (管理员操作通过 service role key)
|
||||
CREATE POLICY "site_config_read_all" ON site_config FOR SELECT USING (true);
|
||||
CREATE POLICY "site_config_admin_write" ON site_config FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- announcements: 所有人可读,认证用户可写 (管理员操作)
|
||||
CREATE POLICY "announcements_read_all" ON announcements FOR SELECT USING (true);
|
||||
CREATE POLICY "announcements_admin_write" ON announcements FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
||||
);
|
||||
|
||||
-- site_stats: 公开读,访问量递增走 SECURITY DEFINER 函数
|
||||
CREATE POLICY "site_stats_read_all" ON site_stats FOR SELECT USING (true);
|
||||
|
||||
-- ============================================================
|
||||
-- Supabase Storage 桶 (通过 Supabase Dashboard 或 API 创建)
|
||||
-- ============================================================
|
||||
-- 需要在 Supabase Dashboard 中手动创建以下 Storage 桶:
|
||||
-- 1. site-assets (公开读) — 存放网站 Logo、Favicon
|
||||
-- 2. works (私有) — 存放用户生成的图片/视频文件
|
||||
--
|
||||
-- 或者通过 SQL (需要 service_role 权限):
|
||||
-- INSERT INTO storage.buckets (id, name, public) VALUES ('site-assets', 'site-assets', true) ON CONFLICT DO NOTHING;
|
||||
-- INSERT INTO storage.buckets (id, name, public) VALUES ('works', 'works', false) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 触发器: 自动更新 updated_at 字段
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS profiles_updated_at ON profiles;
|
||||
DROP TRIGGER IF EXISTS works_updated_at ON works;
|
||||
DROP TRIGGER IF EXISTS orders_updated_at ON orders;
|
||||
DROP TRIGGER IF EXISTS user_api_keys_updated_at ON user_api_keys;
|
||||
DROP TRIGGER IF EXISTS site_config_updated_at ON site_config;
|
||||
DROP TRIGGER IF EXISTS announcements_updated_at ON announcements;
|
||||
|
||||
CREATE TRIGGER profiles_updated_at BEFORE UPDATE ON profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER works_updated_at BEFORE UPDATE ON works FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER orders_updated_at BEFORE UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER user_api_keys_updated_at BEFORE UPDATE ON user_api_keys FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER site_config_updated_at BEFORE UPDATE ON site_config FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
CREATE TRIGGER announcements_updated_at BEFORE UPDATE ON announcements FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- ============================================================
|
||||
-- 触发器: 新用户注册时自动创建 profile
|
||||
-- (仅在使用 Supabase Auth 时生效)
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION handle_new_user()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO profiles (id, email, nickname, role, membership_tier, credits_balance, daily_quota_limit)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.email,
|
||||
COALESCE(NEW.raw_user_meta_data->>'nickname', split_part(NEW.email, '@', 1)),
|
||||
'user',
|
||||
'free',
|
||||
10, -- 新用户赠送 10 积分
|
||||
5 -- 每日配额 5 次
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
-- 记录注册赠送积分
|
||||
INSERT INTO credit_transactions (user_id, amount, balance_after, type, description)
|
||||
SELECT NEW.id, 10, 10, 'gift', '新用户注册奖励'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM credit_transactions
|
||||
WHERE user_id = NEW.id AND type = 'gift' AND description = '新用户注册奖励'
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||||
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION handle_new_user();
|
||||
|
||||
-- ============================================================
|
||||
-- 初始化管理员账户 (可选)
|
||||
-- 请在注册管理员后,手动执行以下 SQL 将角色设为 admin:
|
||||
-- UPDATE profiles SET role = 'admin' WHERE email = 'your-admin@example.com';
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- 原子递增访问量的 SQL 函数
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION increment_visits()
|
||||
RETURNS BIGINT AS $$
|
||||
DECLARE
|
||||
new_count BIGINT;
|
||||
BEGIN
|
||||
UPDATE site_stats SET total_visits = total_visits + 1, updated_at = now() WHERE id = 1
|
||||
RETURNING total_visits INTO new_count;
|
||||
RETURN new_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- 完成
|
||||
SELECT 'Database initialization completed successfully!' AS status;
|
||||
12
scripts/prepare.sh
Normal file
12
scripts/prepare.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
|
||||
echo "Installing dependencies..."
|
||||
pnpm install --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
|
||||
if command -v coze > /dev/null 2>&1 && coze check-bins --help > /dev/null 2>&1; then
|
||||
coze check-bins --fix
|
||||
fi
|
||||
46
scripts/start.sh
Normal file
46
scripts/start.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
|
||||
|
||||
# Load environment variables from .env.local if it exists. PM2 role-specific
|
||||
# values are restored afterwards so backend/console services keep their ports.
|
||||
PM2_DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-}"
|
||||
PM2_APP_RUNTIME_ROLE="${APP_RUNTIME_ROLE:-}"
|
||||
PM2_BACKEND_INTERNAL_URL="${BACKEND_INTERNAL_URL:-}"
|
||||
PM2_CONSOLE_INTERNAL_URL="${CONSOLE_INTERNAL_URL:-}"
|
||||
|
||||
if [ -f "${COZE_WORKSPACE_PATH}/.env.local" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${COZE_WORKSPACE_PATH}/.env.local"
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
[ -n "${PM2_DEPLOY_RUN_PORT}" ] && DEPLOY_RUN_PORT="${PM2_DEPLOY_RUN_PORT}"
|
||||
[ -n "${PM2_APP_RUNTIME_ROLE}" ] && APP_RUNTIME_ROLE="${PM2_APP_RUNTIME_ROLE}"
|
||||
[ -n "${PM2_BACKEND_INTERNAL_URL}" ] && BACKEND_INTERNAL_URL="${PM2_BACKEND_INTERNAL_URL}"
|
||||
[ -n "${PM2_CONSOLE_INTERNAL_URL}" ] && CONSOLE_INTERNAL_URL="${PM2_CONSOLE_INTERNAL_URL}"
|
||||
|
||||
if [ -n "${DEPLOY_NODE_BIN_DIR:-}" ] && [ -d "${DEPLOY_NODE_BIN_DIR}" ]; then
|
||||
export PATH="${DEPLOY_NODE_BIN_DIR}:${PATH}"
|
||||
fi
|
||||
|
||||
PORT=${1:-5000}
|
||||
DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-$PORT}"
|
||||
APP_RUNTIME_ROLE="${APP_RUNTIME_ROLE:-full}"
|
||||
|
||||
|
||||
start_service() {
|
||||
cd "${COZE_WORKSPACE_PATH}"
|
||||
echo "Starting ${APP_RUNTIME_ROLE} HTTP service on port ${DEPLOY_RUN_PORT} for deploy..."
|
||||
echo "COZE_PROJECT_ENV: ${COZE_PROJECT_ENV}"
|
||||
export NODE_ENV="${NODE_ENV:-production}"
|
||||
export APP_RUNTIME_ROLE
|
||||
PORT=${DEPLOY_RUN_PORT} node dist/server.js
|
||||
}
|
||||
|
||||
echo "Starting ${APP_RUNTIME_ROLE} HTTP service on port ${DEPLOY_RUN_PORT} for deploy..."
|
||||
start_service
|
||||
Reference in New Issue
Block a user