first commit

This commit is contained in:
qwer-xyz
2026-06-20 14:22:31 +08:00
commit c2498911ab
793 changed files with 291660 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# 此目录用于存放 Incus mTLS 证书
# 请将 client.crt 和 client.key 放置于此
# 注意:证书文件不应提交到 Git
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
set -e
echo "🔄 Running database migrations..."
cd /app/server
npx prisma migrate deploy
echo "🚀 Starting application..."
cd /app
exec node server/dist/app.js
+6829
View File
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
{
"name": "server",
"version": "1.0.0",
"description": "Incudal 后端服务",
"type": "module",
"main": "dist/app.js",
"types": "dist/app.d.ts",
"scripts": {
"postinstall": "prisma generate",
"dev": "cross-env NODE_ENV=development JWT_SECRET=dev-secret-12345678 PORT=8888 tsx watch src/app.ts",
"start": "node dist/app.js",
"build": "tsc",
"type-check": "tsc --noEmit",
"migrate:data": "tsx scripts/migrate-data.ts",
"backfill:instance-network": "tsx scripts/backfill-instance-network-addresses.ts",
"fix:migrated-billing": "tsx scripts/fix-migrated-instance-billing.ts",
"reset:db": "tsx scripts/reset-db.ts",
"seed:test-data": "tsx scripts/seed-test-data.ts",
"seed:bulk-local-data": "tsx scripts/seed-bulk-local-data.ts",
"update:admin-quota": "tsx scripts/update-admin-quota.ts",
"test:prisma": "tsx scripts/test-prisma.ts",
"test:app": "tsx scripts/test-app-start.ts",
"test:agent-auth": "tsx scripts/test-agent-auth.ts",
"test:security-config": "tsx scripts/test-security-config.ts",
"test:api": "tsx scripts/test-api.ts",
"analyze:queries": "tsx scripts/analyze-queries.ts"
},
"dependencies": {
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^10.0.1",
"@fastify/helmet": "^13.0.2",
"@fastify/jwt": "^9.0.1",
"@fastify/multipart": "^9.0.3",
"@fastify/rate-limit": "^10.3.0",
"@fastify/static": "^8.0.3",
"@fastify/websocket": "^11.0.1",
"@libsql/client": "^0.14.0",
"@prisma/adapter-pg": "^7.1.0",
"@prisma/client": "^7.1.0",
"@types/ws": "^8.18.1",
"basic-ftp": "^5.0.5",
"bcryptjs": "^2.4.3",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.38.2",
"fastify": "^5.1.0",
"fastify-cloudflare-turnstile": "^2.0.2",
"ip-address": "^10.1.0",
"jsonwebtoken": "^9.0.3",
"nanoid": "^5.1.6",
"node-cron": "^4.2.1",
"nodemailer": "^7.0.11",
"otplib": "^12.0.1",
"p-limit": "^7.2.0",
"pg": "^8.16.3",
"pino-pretty": "^13.0.0",
"qrcode": "^1.5.4",
"ssh2-sftp-client": "^11.0.0",
"undici": "^7.1.0",
"webdav": "^5.8.0",
"ws": "^8.18.3"
},
"devDependencies": {
"@types/node": "^24.10.1",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^7.0.4",
"@types/pg": "^8.15.6",
"@types/qrcode": "^1.5.6",
"@types/ssh2-sftp-client": "^9.0.4",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"cross-env": "^10.1.0",
"drizzle-kit": "^0.30.1",
"prisma": "^7.1.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
+15
View File
@@ -0,0 +1,15 @@
// This file was generated by Prisma and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
provider: "postgresql",
},
});
@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "email_verification_codes" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"code" TEXT NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "email_verification_codes_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "email_verification_codes_email_code_idx" ON "email_verification_codes"("email", "code");
-- CreateIndex
CREATE INDEX "email_verification_codes_expires_at_idx" ON "email_verification_codes"("expires_at");
@@ -0,0 +1,535 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('admin', 'user');
-- CreateEnum
CREATE TYPE "UserStatus" AS ENUM ('active', 'banned');
-- CreateEnum
CREATE TYPE "HostStatus" AS ENUM ('online', 'offline', 'maintenance');
-- CreateEnum
CREATE TYPE "InstanceType" AS ENUM ('container', 'vm', 'both');
-- CreateEnum
CREATE TYPE "NetworkMode" AS ENUM ('nat', 'ipv6', 'dual');
-- CreateEnum
CREATE TYPE "InstanceStatus" AS ENUM ('creating', 'running', 'stopped', 'error', 'deleted');
-- CreateEnum
CREATE TYPE "Protocol" AS ENUM ('tcp', 'udp');
-- CreateEnum
CREATE TYPE "BackupStatus" AS ENUM ('creating', 'ready', 'error', 'deleted');
-- CreateEnum
CREATE TYPE "NotificationChannelType" AS ENUM ('telegram', 'discord', 'email', 'webhook');
-- CreateEnum
CREATE TYPE "NotificationLogStatus" AS ENUM ('pending', 'sent', 'failed');
-- CreateEnum
CREATE TYPE "OAuthProvider" AS ENUM ('github', 'google');
-- CreateEnum
CREATE TYPE "HostImageStatus" AS ENUM ('pending', 'syncing', 'ready', 'error');
-- CreateTable
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"email" TEXT,
"password_hash" TEXT NOT NULL,
"role" "UserRole" NOT NULL DEFAULT 'user',
"status" "UserStatus" NOT NULL DEFAULT 'active',
"balance" DECIMAL(10,2) NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_quotas" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"cpu_limit" INTEGER NOT NULL DEFAULT 20,
"cpu_used" INTEGER NOT NULL DEFAULT 0,
"memory_limit" INTEGER NOT NULL DEFAULT 102400,
"memory_used" INTEGER NOT NULL DEFAULT 0,
"disk_limit" INTEGER NOT NULL DEFAULT 512000,
"disk_used" INTEGER NOT NULL DEFAULT 0,
"instance_limit" INTEGER NOT NULL DEFAULT 10,
"instance_used" INTEGER NOT NULL DEFAULT 0,
"port_limit" INTEGER NOT NULL DEFAULT 20,
"port_used" INTEGER NOT NULL DEFAULT 0,
"snapshot_limit" INTEGER NOT NULL DEFAULT 50,
"snapshot_used" INTEGER NOT NULL DEFAULT 0,
"backup_limit" INTEGER NOT NULL DEFAULT 30,
"backup_used" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "user_quotas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "invite_codes" (
"id" SERIAL NOT NULL,
"code" TEXT NOT NULL,
"created_by" INTEGER NOT NULL,
"used_by" INTEGER,
"used_at" TIMESTAMP(3),
"expires_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "invite_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ssh_keys" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"public_key" TEXT NOT NULL,
"fingerprint" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ssh_keys_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "node_groups" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"color" TEXT NOT NULL DEFAULT '#3b82f6',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "node_groups_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "hosts" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"location" TEXT,
"country_code" TEXT NOT NULL DEFAULT 'us',
"node_group_id" INTEGER,
"status" "HostStatus" NOT NULL DEFAULT 'offline',
"cert_path" TEXT,
"key_path" TEXT,
"nat_public_ip" TEXT,
"nat_port_start" INTEGER,
"nat_port_end" INTEGER,
"cpu_total" INTEGER NOT NULL DEFAULT 0,
"cpu_used" INTEGER NOT NULL DEFAULT 0,
"memory_total" INTEGER NOT NULL DEFAULT 0,
"memory_used" INTEGER NOT NULL DEFAULT 0,
"disk_total" INTEGER NOT NULL DEFAULT 0,
"disk_used" INTEGER NOT NULL DEFAULT 0,
"tags" JSONB DEFAULT '[]',
"nat_ports_used_count" INTEGER NOT NULL DEFAULT 0,
"cpu_allowance_max" INTEGER NOT NULL DEFAULT 0,
"memory_max" INTEGER NOT NULL DEFAULT 0,
"instance_type" "InstanceType" NOT NULL DEFAULT 'container',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "hosts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "packages" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"cpu_max" INTEGER NOT NULL,
"memory_max" INTEGER NOT NULL,
"disk_max" INTEGER NOT NULL,
"bandwidth_max" INTEGER,
"network_mode" "NetworkMode" NOT NULL DEFAULT 'nat',
"node_group_id" INTEGER,
"node_selectors" JSONB DEFAULT '[]',
"privileged" BOOLEAN NOT NULL DEFAULT false,
"nested" BOOLEAN NOT NULL DEFAULT false,
"active" BOOLEAN NOT NULL DEFAULT true,
"port_limit" INTEGER NOT NULL DEFAULT 20,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "packages_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "instances" (
"id" SERIAL NOT NULL,
"incus_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"package_id" INTEGER,
"image" TEXT NOT NULL,
"status" "InstanceStatus" NOT NULL DEFAULT 'creating',
"cpu" INTEGER NOT NULL,
"memory" INTEGER NOT NULL,
"disk" INTEGER NOT NULL,
"ipv4" TEXT,
"ipv6" TEXT,
"network_mode" "NetworkMode" NOT NULL DEFAULT 'nat',
"ssh_port" INTEGER,
"root_password" TEXT,
"snapshotted_specs" JSONB DEFAULT '{}',
"port_limit" INTEGER,
"snapshot_limit" INTEGER,
"backup_limit" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "instances_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "port_mappings" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"protocol" "Protocol" NOT NULL DEFAULT 'tcp',
"public_port" INTEGER NOT NULL,
"private_port" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "port_mappings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "snapshots" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"incus_name" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"stateful" BOOLEAN NOT NULL DEFAULT false,
"size" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "snapshots_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "backups" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"incus_name" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"size" INTEGER NOT NULL DEFAULT 0,
"status" "BackupStatus" NOT NULL DEFAULT 'creating',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMP(3),
CONSTRAINT "backups_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "backup_policies" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"interval_hours" INTEGER NOT NULL DEFAULT 24,
"retention_count" INTEGER NOT NULL DEFAULT 3,
"last_run_at" TIMESTAMP(3),
"next_run_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "backup_policies_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "snapshot_policies" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"interval_hours" INTEGER NOT NULL DEFAULT 6,
"retention_count" INTEGER NOT NULL DEFAULT 5,
"last_run_at" TIMESTAMP(3),
"next_run_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "snapshot_policies_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "notification_channels" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "NotificationChannelType" NOT NULL,
"name" TEXT NOT NULL,
"config" JSONB NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "notification_channels_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "notification_logs" (
"id" SERIAL NOT NULL,
"channel_id" INTEGER NOT NULL,
"event_type" TEXT NOT NULL,
"message" TEXT NOT NULL,
"status" "NotificationLogStatus" NOT NULL,
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "notification_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "oauth_configs" (
"id" SERIAL NOT NULL,
"provider" "OAuthProvider" NOT NULL,
"client_id" TEXT NOT NULL,
"client_secret" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "oauth_configs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_oauth_bindings" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"provider" "OAuthProvider" NOT NULL,
"provider_user_id" TEXT NOT NULL,
"provider_username" TEXT,
"provider_email" TEXT,
"provider_avatar" TEXT,
"access_token" TEXT,
"refresh_token" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_oauth_bindings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "help_articles" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"content" TEXT NOT NULL,
"category" TEXT NOT NULL DEFAULT 'general',
"sort_order" INTEGER NOT NULL DEFAULT 0,
"published" BOOLEAN NOT NULL DEFAULT true,
"created_by" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "help_articles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "images" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"os_type" TEXT NOT NULL DEFAULT 'Linux',
"remote_alias" TEXT NOT NULL,
"architecture" TEXT NOT NULL DEFAULT 'x86_64',
"description" TEXT,
"icon" TEXT,
"active" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "images_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "host_images" (
"id" SERIAL NOT NULL,
"host_id" INTEGER NOT NULL,
"image_id" INTEGER NOT NULL,
"fingerprint" TEXT,
"alias" TEXT,
"size" INTEGER NOT NULL DEFAULT 0,
"status" "HostImageStatus" NOT NULL DEFAULT 'pending',
"error_message" TEXT,
"synced_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "host_images_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER,
"module" TEXT NOT NULL,
"action" TEXT NOT NULL,
"content" TEXT NOT NULL,
"result" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
-- CreateIndex
CREATE UNIQUE INDEX "user_quotas_user_id_key" ON "user_quotas"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "invite_codes_code_key" ON "invite_codes"("code");
-- CreateIndex
CREATE INDEX "invite_codes_code_idx" ON "invite_codes"("code");
-- CreateIndex
CREATE INDEX "ssh_keys_user_id_idx" ON "ssh_keys"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "node_groups_name_key" ON "node_groups"("name");
-- CreateIndex
CREATE UNIQUE INDEX "hosts_name_key" ON "hosts"("name");
-- CreateIndex
CREATE INDEX "hosts_node_group_id_idx" ON "hosts"("node_group_id");
-- CreateIndex
CREATE INDEX "instances_user_id_idx" ON "instances"("user_id");
-- CreateIndex
CREATE INDEX "instances_host_id_idx" ON "instances"("host_id");
-- CreateIndex
CREATE INDEX "instances_status_idx" ON "instances"("status");
-- CreateIndex
CREATE INDEX "port_mappings_instance_id_idx" ON "port_mappings"("instance_id");
-- CreateIndex
CREATE INDEX "snapshots_instance_id_idx" ON "snapshots"("instance_id");
-- CreateIndex
CREATE INDEX "backups_instance_id_idx" ON "backups"("instance_id");
-- CreateIndex
CREATE INDEX "backups_status_idx" ON "backups"("status");
-- CreateIndex
CREATE UNIQUE INDEX "backup_policies_instance_id_key" ON "backup_policies"("instance_id");
-- CreateIndex
CREATE UNIQUE INDEX "snapshot_policies_instance_id_key" ON "snapshot_policies"("instance_id");
-- CreateIndex
CREATE INDEX "notification_channels_user_id_idx" ON "notification_channels"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "oauth_configs_provider_key" ON "oauth_configs"("provider");
-- CreateIndex
CREATE INDEX "user_oauth_bindings_user_id_idx" ON "user_oauth_bindings"("user_id");
-- CreateIndex
CREATE INDEX "user_oauth_bindings_provider_provider_user_id_idx" ON "user_oauth_bindings"("provider", "provider_user_id");
-- CreateIndex
CREATE UNIQUE INDEX "user_oauth_bindings_user_id_provider_key" ON "user_oauth_bindings"("user_id", "provider");
-- CreateIndex
CREATE UNIQUE INDEX "user_oauth_bindings_provider_provider_user_id_key" ON "user_oauth_bindings"("provider", "provider_user_id");
-- CreateIndex
CREATE UNIQUE INDEX "help_articles_slug_key" ON "help_articles"("slug");
-- CreateIndex
CREATE INDEX "host_images_host_id_idx" ON "host_images"("host_id");
-- CreateIndex
CREATE INDEX "host_images_image_id_idx" ON "host_images"("image_id");
-- CreateIndex
CREATE UNIQUE INDEX "host_images_host_id_image_id_key" ON "host_images"("host_id", "image_id");
-- CreateIndex
CREATE INDEX "logs_user_id_idx" ON "logs"("user_id");
-- CreateIndex
CREATE INDEX "logs_module_idx" ON "logs"("module");
-- CreateIndex
CREATE INDEX "logs_created_at_idx" ON "logs"("created_at");
-- AddForeignKey
ALTER TABLE "user_quotas" ADD CONSTRAINT "user_quotas_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "invite_codes" ADD CONSTRAINT "invite_codes_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "invite_codes" ADD CONSTRAINT "invite_codes_used_by_fkey" FOREIGN KEY ("used_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ssh_keys" ADD CONSTRAINT "ssh_keys_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "hosts" ADD CONSTRAINT "hosts_node_group_id_fkey" FOREIGN KEY ("node_group_id") REFERENCES "node_groups"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "packages" ADD CONSTRAINT "packages_node_group_id_fkey" FOREIGN KEY ("node_group_id") REFERENCES "node_groups"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "instances" ADD CONSTRAINT "instances_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "instances" ADD CONSTRAINT "instances_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "instances" ADD CONSTRAINT "instances_package_id_fkey" FOREIGN KEY ("package_id") REFERENCES "packages"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "port_mappings" ADD CONSTRAINT "port_mappings_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "port_mappings" ADD CONSTRAINT "port_mappings_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "snapshots" ADD CONSTRAINT "snapshots_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "backups" ADD CONSTRAINT "backups_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "backup_policies" ADD CONSTRAINT "backup_policies_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "snapshot_policies" ADD CONSTRAINT "snapshot_policies_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "notification_channels" ADD CONSTRAINT "notification_channels_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "notification_logs" ADD CONSTRAINT "notification_logs_channel_id_fkey" FOREIGN KEY ("channel_id") REFERENCES "notification_channels"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_oauth_bindings" ADD CONSTRAINT "user_oauth_bindings_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "help_articles" ADD CONSTRAINT "help_articles_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "host_images" ADD CONSTRAINT "host_images_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "host_images" ADD CONSTRAINT "host_images_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "images"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "logs" ADD CONSTRAINT "logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "two_factor_enabled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "two_factor_secret" TEXT;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "two_factor_recovery_codes" TEXT;
@@ -0,0 +1,18 @@
/*
Warnings:
- A unique constraint covering the columns `[install_token]` on the table `hosts` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "enable_api" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "install_token" TEXT,
ADD COLUMN "ip_address" TEXT,
ADD COLUMN "ipv6_gateway" TEXT,
ADD COLUMN "ipv6_mode" INTEGER NOT NULL DEFAULT 1,
ADD COLUMN "ipv6_subnet" TEXT,
ADD COLUMN "is_installed" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "zfs_size" INTEGER NOT NULL DEFAULT 60;
-- CreateIndex
CREATE UNIQUE INDEX "hosts_install_token_key" ON "hosts"("install_token");
@@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "system_configs" (
"id" SERIAL NOT NULL,
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
"type" TEXT NOT NULL DEFAULT 'string',
"label" TEXT,
"description" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "system_configs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "system_configs_key_key" ON "system_configs"("key");
@@ -0,0 +1,52 @@
-- CreateEnum
CREATE TYPE "TrafficStatus" AS ENUM ('NORMAL', 'WARNING', 'LIMITED');
-- AlterTable
ALTER TABLE "instances" ADD COLUMN "monthly_traffic_limit" BIGINT,
ADD COLUMN "monthly_traffic_used" BIGINT NOT NULL DEFAULT 0,
ADD COLUMN "traffic_status" "TrafficStatus" NOT NULL DEFAULT 'NORMAL';
-- AlterTable
ALTER TABLE "user_quotas" ADD COLUMN "extra_traffic_quota" BIGINT NOT NULL DEFAULT 0,
ADD COLUMN "extra_traffic_used" BIGINT NOT NULL DEFAULT 0,
ADD COLUMN "monthly_traffic_limit" BIGINT,
ADD COLUMN "monthly_traffic_used" BIGINT NOT NULL DEFAULT 0,
ADD COLUMN "traffic_status" "TrafficStatus" NOT NULL DEFAULT 'NORMAL',
ADD COLUMN "traffic_warning_sent_at" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "traffic_snapshots" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"rx_raw" BIGINT NOT NULL,
"tx_raw" BIGINT NOT NULL,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "traffic_snapshots_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "daily_traffic" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"date" DATE NOT NULL,
"rx_total" BIGINT NOT NULL,
"tx_total" BIGINT NOT NULL,
CONSTRAINT "daily_traffic_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "traffic_snapshots_instance_id_key" ON "traffic_snapshots"("instance_id");
-- CreateIndex
CREATE INDEX "daily_traffic_instance_id_date_idx" ON "daily_traffic"("instance_id", "date");
-- CreateIndex
CREATE UNIQUE INDEX "daily_traffic_instance_id_date_key" ON "daily_traffic"("instance_id", "date");
-- AddForeignKey
ALTER TABLE "traffic_snapshots" ADD CONSTRAINT "traffic_snapshots_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "daily_traffic" ADD CONSTRAINT "daily_traffic_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "monthly_traffic_limit" BIGINT;
@@ -0,0 +1,30 @@
-- CreateEnum
CREATE TYPE "RestoreTaskStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED');
-- AlterTable
ALTER TABLE "instances" ADD COLUMN "restored_from" JSONB;
-- CreateTable
CREATE TABLE "restore_tasks" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"backup_id" INTEGER,
"host_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"status" "RestoreTaskStatus" NOT NULL DEFAULT 'PENDING',
"temp_instance_name" TEXT,
"original_instance_name" TEXT NOT NULL,
"original_incus_id" TEXT NOT NULL,
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"started_at" TIMESTAMP(3),
"finished_at" TIMESTAMP(3),
CONSTRAINT "restore_tasks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "restore_tasks_host_id_status_idx" ON "restore_tasks"("host_id", "status");
-- CreateIndex
CREATE INDEX "restore_tasks_instance_id_idx" ON "restore_tasks"("instance_id");
@@ -0,0 +1,61 @@
-- CreateEnum
CREATE TYPE "StorageType" AS ENUM ('S3', 'WEBDAV', 'FTP', 'SFTP');
-- CreateEnum
CREATE TYPE "BackupUploadTaskStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED');
-- CreateTable
CREATE TABLE "storage_configs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"type" "StorageType" NOT NULL,
"host" TEXT NOT NULL,
"port" INTEGER,
"username" TEXT,
"password" TEXT,
"base_path" TEXT,
"extra" JSONB,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "storage_configs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "backup_upload_tasks" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"instance_id" INTEGER NOT NULL,
"backup_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"storage_config_id" INTEGER NOT NULL,
"status" "BackupUploadTaskStatus" NOT NULL DEFAULT 'PENDING',
"remote_file_name" TEXT,
"file_size" BIGINT,
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"started_at" TIMESTAMP(3),
"finished_at" TIMESTAMP(3),
CONSTRAINT "backup_upload_tasks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "storage_configs_user_id_idx" ON "storage_configs"("user_id");
-- CreateIndex
CREATE INDEX "backup_upload_tasks_host_id_status_idx" ON "backup_upload_tasks"("host_id", "status");
-- CreateIndex
CREATE INDEX "backup_upload_tasks_user_id_idx" ON "backup_upload_tasks"("user_id");
-- CreateIndex
CREATE INDEX "backup_upload_tasks_instance_id_idx" ON "backup_upload_tasks"("instance_id");
-- AddForeignKey
ALTER TABLE "storage_configs" ADD CONSTRAINT "storage_configs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "backup_upload_tasks" ADD CONSTRAINT "backup_upload_tasks_storage_config_id_fkey" FOREIGN KEY ("storage_config_id") REFERENCES "storage_configs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "backup_upload_tasks_storage_config_id_status_idx" ON "backup_upload_tasks"("storage_config_id", "status");
@@ -0,0 +1,38 @@
-- CreateEnum
CREATE TYPE "TransferStatus" AS ENUM ('pending', 'accepted', 'rejected', 'cancelled');
-- CreateTable
CREATE TABLE "instance_transfers" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"from_user_id" INTEGER NOT NULL,
"to_user_id" INTEGER NOT NULL,
"status" "TransferStatus" NOT NULL DEFAULT 'pending',
"snapshot" JSONB NOT NULL,
"remark" TEXT,
"reject_reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"accepted_at" TIMESTAMP(3),
"rejected_at" TIMESTAMP(3),
"cancelled_at" TIMESTAMP(3),
CONSTRAINT "instance_transfers_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "instance_transfers_from_user_id_status_idx" ON "instance_transfers"("from_user_id", "status");
-- CreateIndex
CREATE INDEX "instance_transfers_to_user_id_status_idx" ON "instance_transfers"("to_user_id", "status");
-- CreateIndex
CREATE INDEX "instance_transfers_instance_id_status_idx" ON "instance_transfers"("instance_id", "status");
-- AddForeignKey
ALTER TABLE "instance_transfers" ADD CONSTRAINT "instance_transfers_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "instance_transfers" ADD CONSTRAINT "instance_transfers_from_user_id_fkey" FOREIGN KEY ("from_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "instance_transfers" ADD CONSTRAINT "instance_transfers_to_user_id_fkey" FOREIGN KEY ("to_user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "avatar_style" TEXT NOT NULL DEFAULT 'bigSmile';
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "port_mappings" ADD COLUMN "remark" TEXT;
@@ -0,0 +1,107 @@
-- 创建好友关系状态枚举
CREATE TYPE "FriendshipStatus" AS ENUM ('pending', 'accepted');
-- 创建好友关系表
CREATE TABLE "friendships" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"friend_id" INTEGER NOT NULL,
"status" "FriendshipStatus" NOT NULL DEFAULT 'pending',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"accepted_at" TIMESTAMP(3),
CONSTRAINT "friendships_pkey" PRIMARY KEY ("id")
);
-- 添加 user_id 列到 hosts 表(先允许 NULL
ALTER TABLE "hosts" ADD COLUMN "user_id" INTEGER;
-- 添加 user_id 列到 images 表(先允许 NULL
ALTER TABLE "images" ADD COLUMN "user_id" INTEGER;
-- 添加 user_id 列到 packages 表(先允许 NULL
ALTER TABLE "packages" ADD COLUMN "user_id" INTEGER;
-- 添加 user_id 列到 node_groups 表(先允许 NULL
ALTER TABLE "node_groups" ADD COLUMN "user_id" INTEGER;
-- 将现有数据的 user_id 设置为第一个管理员用户
-- 如果没有管理员用户,则设置为第一个用户
UPDATE "hosts" SET "user_id" = (
SELECT "id" FROM "users" WHERE "role" = 'admin' ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "images" SET "user_id" = (
SELECT "id" FROM "users" WHERE "role" = 'admin' ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "packages" SET "user_id" = (
SELECT "id" FROM "users" WHERE "role" = 'admin' ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "node_groups" SET "user_id" = (
SELECT "id" FROM "users" WHERE "role" = 'admin' ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
-- 如果仍有 NULL 值(没有管理员用户的情况),使用第一个用户
UPDATE "hosts" SET "user_id" = (
SELECT "id" FROM "users" ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "images" SET "user_id" = (
SELECT "id" FROM "users" ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "packages" SET "user_id" = (
SELECT "id" FROM "users" ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
UPDATE "node_groups" SET "user_id" = (
SELECT "id" FROM "users" ORDER BY "id" LIMIT 1
) WHERE "user_id" IS NULL;
-- 删除 hosts 表原有的唯一约束
DROP INDEX IF EXISTS "hosts_name_key";
-- 删除 node_groups 表原有的唯一约束
DROP INDEX IF EXISTS "node_groups_name_key";
-- 设置 NOT NULL 约束
ALTER TABLE "hosts" ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "images" ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "packages" ALTER COLUMN "user_id" SET NOT NULL;
ALTER TABLE "node_groups" ALTER COLUMN "user_id" SET NOT NULL;
-- 添加外键约束
ALTER TABLE "friendships" ADD CONSTRAINT "friendships_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "friendships" ADD CONSTRAINT "friendships_friend_id_fkey"
FOREIGN KEY ("friend_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "hosts" ADD CONSTRAINT "hosts_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "images" ADD CONSTRAINT "images_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "packages" ADD CONSTRAINT "packages_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "node_groups" ADD CONSTRAINT "node_groups_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- 添加唯一约束
CREATE UNIQUE INDEX "friendships_user_id_friend_id_key" ON "friendships"("user_id", "friend_id");
CREATE UNIQUE INDEX "hosts_user_id_name_key" ON "hosts"("user_id", "name");
CREATE UNIQUE INDEX "images_user_id_name_key" ON "images"("user_id", "name");
CREATE UNIQUE INDEX "packages_user_id_name_key" ON "packages"("user_id", "name");
CREATE UNIQUE INDEX "node_groups_user_id_name_key" ON "node_groups"("user_id", "name");
-- 添加索引
CREATE INDEX "friendships_friend_id_status_idx" ON "friendships"("friend_id", "status");
CREATE INDEX "friendships_user_id_status_idx" ON "friendships"("user_id", "status");
CREATE INDEX "hosts_user_id_idx" ON "hosts"("user_id");
CREATE INDEX "images_user_id_idx" ON "images"("user_id");
CREATE INDEX "packages_user_id_idx" ON "packages"("user_id");
CREATE INDEX "node_groups_user_id_idx" ON "node_groups"("user_id");
@@ -0,0 +1,6 @@
-- AlterEnum
ALTER TYPE "FriendshipStatus" ADD VALUE 'rejected';
-- AlterTable
ALTER TABLE "friendships" ADD COLUMN "rejected_at" TIMESTAMP(3),
ADD COLUMN "remark" TEXT;
@@ -0,0 +1,63 @@
/*
Warnings:
- You are about to drop the column `backup_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `backup_used` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `cpu_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `cpu_used` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `disk_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `disk_used` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `memory_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `memory_used` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `port_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `port_used` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `snapshot_limit` on the `user_quotas` table. All the data in the column will be lost.
- You are about to drop the column `snapshot_used` on the `user_quotas` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "user_quotas" DROP COLUMN "backup_limit",
DROP COLUMN "backup_used",
DROP COLUMN "cpu_limit",
DROP COLUMN "cpu_used",
DROP COLUMN "disk_limit",
DROP COLUMN "disk_used",
DROP COLUMN "memory_limit",
DROP COLUMN "memory_used",
DROP COLUMN "port_limit",
DROP COLUMN "port_used",
DROP COLUMN "snapshot_limit",
DROP COLUMN "snapshot_used",
ALTER COLUMN "instance_limit" SET DEFAULT 50;
-- CreateTable
CREATE TABLE "package_shares" (
"id" SERIAL NOT NULL,
"package_id" INTEGER NOT NULL,
"owner_id" INTEGER NOT NULL,
"shared_to_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "package_shares_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "package_shares_shared_to_id_idx" ON "package_shares"("shared_to_id");
-- CreateIndex
CREATE INDEX "package_shares_owner_id_idx" ON "package_shares"("owner_id");
-- CreateIndex
CREATE INDEX "package_shares_package_id_idx" ON "package_shares"("package_id");
-- CreateIndex
CREATE UNIQUE INDEX "package_shares_package_id_shared_to_id_key" ON "package_shares"("package_id", "shared_to_id");
-- AddForeignKey
ALTER TABLE "package_shares" ADD CONSTRAINT "package_shares_package_id_fkey" FOREIGN KEY ("package_id") REFERENCES "packages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "package_shares" ADD CONSTRAINT "package_shares_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "package_shares" ADD CONSTRAINT "package_shares_shared_to_id_fkey" FOREIGN KEY ("shared_to_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to drop the column `zfs_size` on the `hosts` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "hosts" DROP COLUMN "zfs_size",
ADD COLUMN "install_token_expire" TIMESTAMP(3),
ADD COLUMN "storage_driver" TEXT NOT NULL DEFAULT 'zfs',
ADD COLUMN "storage_path" TEXT,
ADD COLUMN "storage_size" INTEGER NOT NULL DEFAULT 60,
ADD COLUMN "storage_type" TEXT NOT NULL DEFAULT 'loop',
ADD COLUMN "sysctl_config" TEXT;
@@ -0,0 +1,67 @@
-- 配额系统重构迁移
-- 用户配额从 CPU/内存/磁盘/端口/快照/备份 改为 宿主机数量/实例数量/好友数量
-- 套餐新增 snapshotLimit 和 backupLimit 字段
-- =====================================================
-- 1. 修改 user_quotas 表
-- =====================================================
-- 添加新字段
ALTER TABLE "user_quotas" ADD COLUMN IF NOT EXISTS "host_limit" INTEGER NOT NULL DEFAULT 5;
ALTER TABLE "user_quotas" ADD COLUMN IF NOT EXISTS "host_used" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "user_quotas" ADD COLUMN IF NOT EXISTS "friend_limit" INTEGER NOT NULL DEFAULT 20;
ALTER TABLE "user_quotas" ADD COLUMN IF NOT EXISTS "friend_used" INTEGER NOT NULL DEFAULT 0;
-- 更新 instance_limit 默认值为 50(可选,不影响现有数据)
-- ALTER TABLE "user_quotas" ALTER COLUMN "instance_limit" SET DEFAULT 50;
-- 统计并更新现有用户的 host_used
UPDATE "user_quotas" q SET "host_used" = (
SELECT COUNT(*) FROM "hosts" h WHERE h."user_id" = q."user_id"
);
-- 统计并更新现有用户的 instance_used
UPDATE "user_quotas" q SET "instance_used" = (
SELECT COUNT(*) FROM "instances" i WHERE i."user_id" = q."user_id" AND i."status" != 'deleted'
);
-- 统计并更新现有用户的 friend_used(已接受的好友数)
UPDATE "user_quotas" q SET "friend_used" = (
SELECT COUNT(*) FROM "friendships" f
WHERE (f."user_id" = q."user_id" OR f."friend_id" = q."user_id")
AND f."status" = 'accepted'
);
-- 删除旧字段(可选,建议先保留一段时间后再删除)
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "cpu_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "cpu_used";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "memory_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "memory_used";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "disk_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "disk_used";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "port_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "port_used";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "snapshot_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "snapshot_used";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "backup_limit";
-- ALTER TABLE "user_quotas" DROP COLUMN IF EXISTS "backup_used";
-- =====================================================
-- 2. 修改 packages 表
-- =====================================================
-- 添加新字段
ALTER TABLE "packages" ADD COLUMN IF NOT EXISTS "snapshot_limit" INTEGER NOT NULL DEFAULT 5;
ALTER TABLE "packages" ADD COLUMN IF NOT EXISTS "backup_limit" INTEGER NOT NULL DEFAULT 3;
-- =====================================================
-- 3. 更新实例配额(从套餐继承)
-- =====================================================
-- 对于有套餐的实例,如果 snapshot_limit 或 backup_limit 为 NULL,则从套餐继承
UPDATE "instances" i SET
"snapshot_limit" = COALESCE(i."snapshot_limit", p."snapshot_limit"),
"backup_limit" = COALESCE(i."backup_limit", p."backup_limit")
FROM "packages" p
WHERE i."package_id" = p."id"
AND (i."snapshot_limit" IS NULL OR i."backup_limit" IS NULL);
@@ -0,0 +1,11 @@
-- 清理系统配置表中的旧配额字段
-- 删除不再使用的 CPU/内存/磁盘/端口/快照/备份 配额配置
DELETE FROM "system_configs" WHERE "key" IN (
'default_quota_cpu',
'default_quota_memory',
'default_quota_disk',
'default_quota_port',
'default_quota_snapshot',
'default_quota_backup'
);
@@ -0,0 +1,59 @@
/*
迁移说明:
1. 创建 package_hosts 表(套餐与宿主机的多对多关联)
2. 迁移数据:将基于 node_group_id 的绑定转换为精确的宿主机绑定
3. 删除 node_group_id 字段和 node_groups 表
4. 对于没有绑定节点组的套餐,绑定套餐所有者的所有宿主机
*/
-- 步骤1: 创建 package_hosts 表
CREATE TABLE "package_hosts" (
"id" SERIAL NOT NULL,
"package_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "package_hosts_pkey" PRIMARY KEY ("id")
);
-- 步骤2: 创建索引和外键
CREATE INDEX "package_hosts_package_id_idx" ON "package_hosts"("package_id");
CREATE INDEX "package_hosts_host_id_idx" ON "package_hosts"("host_id");
CREATE UNIQUE INDEX "package_hosts_package_id_host_id_key" ON "package_hosts"("package_id", "host_id");
ALTER TABLE "package_hosts" ADD CONSTRAINT "package_hosts_package_id_fkey" FOREIGN KEY ("package_id") REFERENCES "packages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "package_hosts" ADD CONSTRAINT "package_hosts_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- 步骤3: 迁移数据
-- 3.1: 对于有 node_group_id 的套餐,绑定该节点组下的所有宿主机
INSERT INTO "package_hosts" ("package_id", "host_id", "created_at")
SELECT DISTINCT p.id, h.id, CURRENT_TIMESTAMP
FROM "packages" p
INNER JOIN "hosts" h ON h.node_group_id = p.node_group_id
WHERE p.node_group_id IS NOT NULL;
-- 3.2: 对于没有 node_group_id 的套餐,绑定套餐所有者的所有宿主机
INSERT INTO "package_hosts" ("package_id", "host_id", "created_at")
SELECT DISTINCT p.id, h.id, CURRENT_TIMESTAMP
FROM "packages" p
INNER JOIN "hosts" h ON h.user_id = p.user_id
WHERE p.node_group_id IS NULL
AND NOT EXISTS (
SELECT 1 FROM "package_hosts" ph
WHERE ph.package_id = p.id AND ph.host_id = h.id
);
-- 步骤4: 删除外键约束
ALTER TABLE "hosts" DROP CONSTRAINT IF EXISTS "hosts_node_group_id_fkey";
ALTER TABLE "node_groups" DROP CONSTRAINT IF EXISTS "node_groups_user_id_fkey";
ALTER TABLE "packages" DROP CONSTRAINT IF EXISTS "packages_node_group_id_fkey";
-- 步骤5: 删除索引
DROP INDEX IF EXISTS "hosts_node_group_id_idx";
-- 步骤6: 删除字段
ALTER TABLE "hosts" DROP COLUMN IF EXISTS "node_group_id";
ALTER TABLE "packages" DROP COLUMN IF EXISTS "node_group_id";
-- 步骤7: 删除 node_groups 表
DROP TABLE IF EXISTS "node_groups";
@@ -0,0 +1,76 @@
-- 简化镜像管理:移除 Image 表,直接在 HostImage 中存储镜像信息
-- 1. 备份现有数据(如果需要恢复)
-- 创建临时表保存现有的 host_images 数据
CREATE TEMP TABLE temp_host_images AS
SELECT
hi.id,
hi.host_id,
i.remote_alias as image_id,
i.name,
i.os_type,
i.remote_alias,
COALESCE(i.icon, SPLIT_PART(i.remote_alias, '/', 1)) as icon,
hi.fingerprint,
hi.alias,
hi.size,
hi.status,
hi.error_message,
hi.synced_at,
hi.created_at,
hi.updated_at
FROM host_images hi
JOIN images i ON hi.image_id = i.id;
-- 2. 删除外键约束和旧表
DROP TABLE IF EXISTS host_images;
DROP TABLE IF EXISTS images;
-- 3. 创建新的 host_images 表
CREATE TABLE host_images (
id SERIAL PRIMARY KEY,
host_id INTEGER NOT NULL,
image_id VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL,
os_type VARCHAR(50) NOT NULL DEFAULT 'Linux',
remote_alias VARCHAR(200) NOT NULL,
icon VARCHAR(50),
fingerprint VARCHAR(100),
alias VARCHAR(100),
size INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
error_message TEXT,
synced_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_host_images_host FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,
CONSTRAINT uq_host_images_host_image UNIQUE (host_id, image_id)
);
-- 4. 创建索引
CREATE INDEX idx_host_images_host_id ON host_images(host_id);
-- 5. 恢复数据(将 remote_alias 转换为 image_id 格式)
INSERT INTO host_images (
host_id, image_id, name, os_type, remote_alias, icon,
fingerprint, alias, size, status, error_message, synced_at, created_at, updated_at
)
SELECT
host_id,
LOWER(REPLACE(REPLACE(remote_alias, '/', '-'), '.', '-')) as image_id,
name,
os_type,
remote_alias,
icon,
fingerprint,
alias,
size,
status::VARCHAR,
error_message,
synced_at,
created_at,
updated_at
FROM temp_host_images;
-- 6. 清理临时表
DROP TABLE temp_host_images;
@@ -0,0 +1,60 @@
/*
Warnings:
- The `status` column on the `host_images` table would be dropped and recreated. This will lead to data loss if there is data in the column.
*/
-- DropForeignKey
ALTER TABLE "host_images" DROP CONSTRAINT "fk_host_images_host";
-- AlterTable
ALTER TABLE "host_images" ALTER COLUMN "image_id" SET DATA TYPE TEXT,
ALTER COLUMN "name" SET DATA TYPE TEXT,
ALTER COLUMN "os_type" SET DATA TYPE TEXT,
ALTER COLUMN "remote_alias" SET DATA TYPE TEXT,
ALTER COLUMN "icon" SET DATA TYPE TEXT,
ALTER COLUMN "fingerprint" SET DATA TYPE TEXT,
ALTER COLUMN "alias" SET DATA TYPE TEXT,
DROP COLUMN "status",
ADD COLUMN "status" "HostImageStatus" NOT NULL DEFAULT 'pending',
ALTER COLUMN "synced_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "created_at" SET DATA TYPE TIMESTAMP(3),
ALTER COLUMN "updated_at" DROP DEFAULT,
ALTER COLUMN "updated_at" SET DATA TYPE TIMESTAMP(3);
-- AlterTable
ALTER TABLE "instances" ADD COLUMN "boot_autostart" BOOLEAN,
ADD COLUMN "boot_autostart_delay" INTEGER,
ADD COLUMN "boot_autostart_priority" INTEGER,
ADD COLUMN "boot_host_shutdown_timeout" INTEGER,
ADD COLUMN "limits_cpu_priority" INTEGER,
ADD COLUMN "limits_egress" TEXT,
ADD COLUMN "limits_ingress" TEXT,
ADD COLUMN "limits_processes" INTEGER,
ADD COLUMN "limits_read" TEXT,
ADD COLUMN "limits_read_iops" INTEGER,
ADD COLUMN "limits_write" TEXT,
ADD COLUMN "limits_write_iops" INTEGER;
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "boot_autostart" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "boot_autostart_delay" INTEGER NOT NULL DEFAULT 15,
ADD COLUMN "boot_autostart_priority" INTEGER NOT NULL DEFAULT 20,
ADD COLUMN "boot_host_shutdown_timeout" INTEGER NOT NULL DEFAULT 30,
ADD COLUMN "limits_cpu_priority" INTEGER NOT NULL DEFAULT 10,
ADD COLUMN "limits_egress" TEXT NOT NULL DEFAULT '300Mbit',
ADD COLUMN "limits_ingress" TEXT NOT NULL DEFAULT '300Mbit',
ADD COLUMN "limits_processes" INTEGER NOT NULL DEFAULT 500,
ADD COLUMN "limits_read" TEXT NOT NULL DEFAULT '100MB',
ADD COLUMN "limits_read_iops" INTEGER NOT NULL DEFAULT 500,
ADD COLUMN "limits_write" TEXT NOT NULL DEFAULT '100MB',
ADD COLUMN "limits_write_iops" INTEGER NOT NULL DEFAULT 500;
-- AddForeignKey
ALTER TABLE "host_images" ADD CONSTRAINT "host_images_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RenameIndex
ALTER INDEX "idx_host_images_host_id" RENAME TO "host_images_host_id_idx";
-- RenameIndex
ALTER INDEX "uq_host_images_host_image" RENAME TO "host_images_host_id_image_id_key";
@@ -0,0 +1 @@
-- This is an empty migration.
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to drop the `host_images` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "host_images" DROP CONSTRAINT "host_images_host_id_fkey";
-- DropTable
DROP TABLE "host_images";
-- DropEnum
DROP TYPE "HostImageStatus";
@@ -0,0 +1 @@
-- This is an empty migration.
@@ -0,0 +1,32 @@
-- CreateEnum (with IF NOT EXISTS to handle partial migrations)
DO $$ BEGIN
CREATE TYPE "IpType" AS ENUM ('inet4', 'inet6');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- CreateTable (with IF NOT EXISTS)
CREATE TABLE IF NOT EXISTS "ip_addresses" (
"id" SERIAL NOT NULL,
"address" TEXT NOT NULL,
"type" "IpType" NOT NULL DEFAULT 'inet6',
"is_primary" BOOLEAN NOT NULL DEFAULT false,
"device" TEXT NOT NULL DEFAULT 'eth0',
"instance_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ip_addresses_pkey" PRIMARY KEY ("id")
);
-- CreateIndex (with IF NOT EXISTS)
CREATE UNIQUE INDEX IF NOT EXISTS "ip_addresses_address_key" ON "ip_addresses"("address");
-- CreateIndex (with IF NOT EXISTS)
CREATE INDEX IF NOT EXISTS "ip_addresses_instance_id_idx" ON "ip_addresses"("instance_id");
-- AddForeignKey (check if not exists)
DO $$ BEGIN
ALTER TABLE "ip_addresses" ADD CONSTRAINT "ip_addresses_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
@@ -0,0 +1,56 @@
-- CreateTable
CREATE TABLE "refresh_tokens" (
"id" SERIAL NOT NULL,
"token" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
"username" TEXT NOT NULL,
"role" "UserRole" NOT NULL,
"ip" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMP(3) NOT NULL,
"last_active_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "token_invalidations" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"session_id" TEXT,
"invalidated_at" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "token_invalidations_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "refresh_tokens_token_key" ON "refresh_tokens"("token");
-- CreateIndex
CREATE INDEX "refresh_tokens_user_id_idx" ON "refresh_tokens"("user_id");
-- CreateIndex
CREATE INDEX "refresh_tokens_token_idx" ON "refresh_tokens"("token");
-- CreateIndex
CREATE INDEX "refresh_tokens_expires_at_idx" ON "refresh_tokens"("expires_at");
-- CreateIndex
CREATE INDEX "token_invalidations_user_id_idx" ON "token_invalidations"("user_id");
-- CreateIndex
CREATE INDEX "token_invalidations_session_id_idx" ON "token_invalidations"("session_id");
-- CreateIndex
CREATE INDEX "token_invalidations_invalidated_at_idx" ON "token_invalidations"("invalidated_at");
-- CreateIndex
CREATE UNIQUE INDEX "token_invalidations_user_id_session_id_key" ON "token_invalidations"("user_id", "session_id");
-- AddForeignKey
ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "token_invalidations" ADD CONSTRAINT "token_invalidations_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "FriendshipStatus" ADD VALUE 'removed';
@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `cpu_total` on the `hosts` table. All the data in the column will be lost.
- You are about to drop the column `disk_total` on the `hosts` table. All the data in the column will be lost.
- You are about to drop the column `memory_total` on the `hosts` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "hosts" DROP COLUMN "cpu_total",
DROP COLUMN "disk_total",
DROP COLUMN "memory_total";
@@ -0,0 +1,7 @@
-- Add progress and retryCount fields to RestoreTask
ALTER TABLE "restore_tasks" ADD COLUMN "progress" TEXT;
ALTER TABLE "restore_tasks" ADD COLUMN "retry_count" INTEGER NOT NULL DEFAULT 0;
-- Add progress and retryCount fields to BackupUploadTask
ALTER TABLE "backup_upload_tasks" ADD COLUMN "progress" TEXT;
ALTER TABLE "backup_upload_tasks" ADD COLUMN "retry_count" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,2 @@
-- Add cert_download_count field to hosts table
ALTER TABLE "hosts" ADD COLUMN "cert_download_count" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,51 @@
-- CreateEnum
CREATE TYPE "OperationType" AS ENUM ('change_password', 'disable_2fa', 'change_email', 'delete_account', 'delete_instance', 'reinstall_instance', 'transfer_instance', 'delete_snapshot', 'delete_backup');
-- CreateEnum
CREATE TYPE "VerificationChannel" AS ENUM ('email', 'telegram', 'discord', 'webhook');
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "cert_download_expire" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "operation_verifications" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"operation_type" "OperationType" NOT NULL,
"code" TEXT NOT NULL,
"channel" "VerificationChannel" NOT NULL,
"resource_id" INTEGER,
"resource_type" TEXT,
"verified" BOOLEAN NOT NULL DEFAULT false,
"verified_at" TIMESTAMP(3),
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "operation_verifications_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "distributed_locks" (
"id" SERIAL NOT NULL,
"lock_key" TEXT NOT NULL,
"owner_id" TEXT NOT NULL,
"acquired_at" TIMESTAMP(3) NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "distributed_locks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "operation_verifications_user_id_operation_type_code_idx" ON "operation_verifications"("user_id", "operation_type", "code");
-- CreateIndex
CREATE INDEX "operation_verifications_expires_at_idx" ON "operation_verifications"("expires_at");
-- CreateIndex
CREATE UNIQUE INDEX "distributed_locks_lock_key_key" ON "distributed_locks"("lock_key");
-- CreateIndex
CREATE INDEX "distributed_locks_expires_at_idx" ON "distributed_locks"("expires_at");
-- AddForeignKey
ALTER TABLE "operation_verifications" ADD CONSTRAINT "operation_verifications_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,85 @@
-- 修改 NetworkMode 枚举:添加新值并重命名
-- 旧值: nat, ipv6, dual
-- 新值: nat, nat_ipv6, ipv4, ipv4_ipv6
-- 注意: PostgreSQL 不允许在同一事务中添加新枚举值后立即使用,所以使用替换枚举类型的方法
-- 1. 创建新的枚举类型
CREATE TYPE "NetworkMode_new" AS ENUM ('nat', 'nat_ipv6', 'ipv4', 'ipv4_ipv6');
-- 2. 修改 packages 表:先删除默认值,转换类型,再恢复默认值
ALTER TABLE "packages" ALTER COLUMN "network_mode" DROP DEFAULT;
ALTER TABLE "packages" ALTER COLUMN "network_mode" TYPE TEXT;
UPDATE "packages" SET "network_mode" = 'nat_ipv6' WHERE "network_mode" = 'ipv6';
UPDATE "packages" SET "network_mode" = 'ipv4_ipv6' WHERE "network_mode" = 'dual';
ALTER TABLE "packages" ALTER COLUMN "network_mode" TYPE "NetworkMode_new" USING "network_mode"::"NetworkMode_new";
ALTER TABLE "packages" ALTER COLUMN "network_mode" SET DEFAULT 'nat'::"NetworkMode_new";
-- 3. 修改 instances 表:先删除默认值,转换类型,再恢复默认值
ALTER TABLE "instances" ALTER COLUMN "network_mode" DROP DEFAULT;
ALTER TABLE "instances" ALTER COLUMN "network_mode" TYPE TEXT;
UPDATE "instances" SET "network_mode" = 'nat_ipv6' WHERE "network_mode" = 'ipv6';
UPDATE "instances" SET "network_mode" = 'ipv4_ipv6' WHERE "network_mode" = 'dual';
ALTER TABLE "instances" ALTER COLUMN "network_mode" TYPE "NetworkMode_new" USING "network_mode"::"NetworkMode_new";
ALTER TABLE "instances" ALTER COLUMN "network_mode" SET DEFAULT 'nat'::"NetworkMode_new";
-- 4. 删除旧枚举类型,重命名新类型
DROP TYPE "NetworkMode";
ALTER TYPE "NetworkMode_new" RENAME TO "NetworkMode";
-- CreateTable
CREATE TABLE "system_images" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"remote_alias" TEXT NOT NULL,
"os_type" TEXT NOT NULL DEFAULT 'Linux',
"architecture" TEXT NOT NULL DEFAULT 'x86_64',
"icon" TEXT NOT NULL,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"hidden" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "system_images_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "system_images_remote_alias_key" ON "system_images"("remote_alias");
-- 插入默认镜像数据(remote_alias 包含完整的源前缀)
INSERT INTO "system_images" ("name", "remote_alias", "os_type", "architecture", "icon", "sort_order", "updated_at") VALUES
-- AlmaLinux
('AlmaLinux 8', 'images:almalinux/8/cloud', 'Linux', 'x86_64', 'almalinux', 10, NOW()),
('AlmaLinux 9', 'images:almalinux/9/cloud', 'Linux', 'x86_64', 'almalinux', 11, NOW()),
('AlmaLinux 10', 'images:almalinux/10/cloud', 'Linux', 'x86_64', 'almalinux', 12, NOW()),
-- Alpine
('Alpine 3.19', 'images:alpine/3.19/cloud', 'Linux', 'x86_64', 'alpine', 20, NOW()),
('Alpine 3.20', 'images:alpine/3.20/cloud', 'Linux', 'x86_64', 'alpine', 21, NOW()),
('Alpine 3.21', 'images:alpine/3.21/cloud', 'Linux', 'x86_64', 'alpine', 22, NOW()),
('Alpine Edge', 'images:alpine/edge/cloud', 'Linux', 'x86_64', 'alpine', 29, NOW()),
-- CentOS
('CentOS 9 Stream', 'images:centos/9-Stream/cloud', 'Linux', 'x86_64', 'centos', 30, NOW()),
('CentOS 10 Stream', 'images:centos/10-Stream/cloud', 'Linux', 'x86_64', 'centos', 31, NOW()),
-- Debian
('Debian 11 (Bullseye)', 'images:debian/11/cloud', 'Linux', 'x86_64', 'debian', 40, NOW()),
('Debian 12 (Bookworm)', 'images:debian/12/cloud', 'Linux', 'x86_64', 'debian', 41, NOW()),
('Debian 13 (Trixie)', 'images:debian/13/cloud', 'Linux', 'x86_64', 'debian', 42, NOW()),
-- Fedora
('Fedora 41', 'images:fedora/41/cloud', 'Linux', 'x86_64', 'fedora', 50, NOW()),
('Fedora 42', 'images:fedora/42/cloud', 'Linux', 'x86_64', 'fedora', 51, NOW()),
-- Kali
('Kali Linux', 'images:kali/cloud', 'Linux', 'x86_64', 'kali', 60, NOW()),
-- openSUSE
('openSUSE 15.6', 'images:opensuse/15.6/cloud', 'Linux', 'x86_64', 'opensuse', 70, NOW()),
('openSUSE Tumbleweed', 'images:opensuse/tumbleweed/cloud', 'Linux', 'x86_64', 'opensuse', 79, NOW()),
-- Oracle
('Oracle Linux 8', 'images:oracle/8/cloud', 'Linux', 'x86_64', 'oracle', 80, NOW()),
('Oracle Linux 9', 'images:oracle/9/cloud', 'Linux', 'x86_64', 'oracle', 81, NOW()),
-- Rocky Linux
('Rocky Linux 8', 'images:rockylinux/8/cloud', 'Linux', 'x86_64', 'rockylinux', 90, NOW()),
('Rocky Linux 9', 'images:rockylinux/9/cloud', 'Linux', 'x86_64', 'rockylinux', 91, NOW()),
('Rocky Linux 10', 'images:rockylinux/10/cloud', 'Linux', 'x86_64', 'rockylinux', 92, NOW()),
-- Ubuntu
('Ubuntu 22.04 (Jammy)', 'images:ubuntu/jammy/cloud', 'Linux', 'x86_64', 'ubuntu', 100, NOW()),
('Ubuntu 24.04 (Noble)', 'images:ubuntu/noble/cloud', 'Linux', 'x86_64', 'ubuntu', 101, NOW()),
('Ubuntu 25.04 (Plucky)', 'images:ubuntu/25.04/cloud', 'Linux', 'x86_64', 'ubuntu', 102, NOW()),
('Ubuntu 25.10 (Questing)', 'images:ubuntu/25.10/cloud', 'Linux', 'x86_64', 'ubuntu', 103, NOW());
@@ -0,0 +1,31 @@
-- 自动快照/备份策略重构迁移
-- 1. 将 interval_hours 改为 interval_minutes(支持更细粒度的频率设置)
-- 2. 移除 retention_count(额度直接继承自套餐包)
-- =====================================================
-- 1. 修改 snapshot_policies 表
-- =====================================================
-- 添加 interval_minutes 字段,默认值为原 interval_hours * 60
ALTER TABLE "snapshot_policies" ADD COLUMN "interval_minutes" INTEGER NOT NULL DEFAULT 360;
-- 迁移现有数据:将 interval_hours 转换为分钟
UPDATE "snapshot_policies" SET "interval_minutes" = "interval_hours" * 60;
-- 删除旧字段
ALTER TABLE "snapshot_policies" DROP COLUMN "interval_hours";
ALTER TABLE "snapshot_policies" DROP COLUMN "retention_count";
-- =====================================================
-- 2. 修改 backup_policies 表
-- =====================================================
-- 添加 interval_minutes 字段,默认值为原 interval_hours * 60
ALTER TABLE "backup_policies" ADD COLUMN "interval_minutes" INTEGER NOT NULL DEFAULT 1440;
-- 迁移现有数据:将 interval_hours 转换为分钟
UPDATE "backup_policies" SET "interval_minutes" = "interval_hours" * 60;
-- 删除旧字段
ALTER TABLE "backup_policies" DROP COLUMN "interval_hours";
ALTER TABLE "backup_policies" DROP COLUMN "retention_count";
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[host_id,protocol,public_port]` on the table `port_mappings` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "port_mappings_host_id_protocol_public_port_key" ON "port_mappings"("host_id", "protocol", "public_port");
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "package_shares" ADD COLUMN "max_instances" INTEGER,
ADD COLUMN "quota_multiplier" DECIMAL(3,1);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "instance_type" "InstanceType" NOT NULL DEFAULT 'container';
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "system_images" ADD COLUMN "instance_type" TEXT NOT NULL DEFAULT 'both';
@@ -0,0 +1,27 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "ipv6_parent_interface" TEXT;
-- AlterTable
ALTER TABLE "ip_addresses" ADD COLUMN "is_custom" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "device" SET DEFAULT 'eth1';
-- CreateTable
CREATE TABLE "ipv6_subnets" (
"id" SERIAL NOT NULL,
"cidr" TEXT NOT NULL,
"primary_ip" TEXT NOT NULL,
"device" TEXT NOT NULL DEFAULT 'eth1',
"instance_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ipv6_subnets_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ipv6_subnets_cidr_key" ON "ipv6_subnets"("cidr");
-- CreateIndex
CREATE INDEX "ipv6_subnets_instance_id_idx" ON "ipv6_subnets"("instance_id");
-- AddForeignKey
ALTER TABLE "ipv6_subnets" ADD CONSTRAINT "ipv6_subnets_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,46 @@
/*
Warnings:
- The values [ipv4,ipv4_ipv6] on the enum `NetworkMode` will be removed. If these variants are still used in the database, this will fail.
*/
-- CreateEnum
CREATE TYPE "StoragePurpose" AS ENUM ('instance_data', 'instance_storage');
-- AlterEnum
BEGIN;
CREATE TYPE "NetworkMode_new" AS ENUM ('nat', 'nat_ipv6');
ALTER TABLE "public"."instances" ALTER COLUMN "network_mode" DROP DEFAULT;
ALTER TABLE "public"."packages" ALTER COLUMN "network_mode" DROP DEFAULT;
ALTER TABLE "packages" ALTER COLUMN "network_mode" TYPE "NetworkMode_new" USING ("network_mode"::text::"NetworkMode_new");
ALTER TABLE "instances" ALTER COLUMN "network_mode" TYPE "NetworkMode_new" USING ("network_mode"::text::"NetworkMode_new");
ALTER TYPE "NetworkMode" RENAME TO "NetworkMode_old";
ALTER TYPE "NetworkMode_new" RENAME TO "NetworkMode";
DROP TYPE "public"."NetworkMode_old";
ALTER TABLE "instances" ALTER COLUMN "network_mode" SET DEFAULT 'nat';
ALTER TABLE "packages" ALTER COLUMN "network_mode" SET DEFAULT 'nat';
COMMIT;
-- CreateTable
CREATE TABLE "storage_pools" (
"id" SERIAL NOT NULL,
"host_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"driver" TEXT NOT NULL,
"purpose" "StoragePurpose" NOT NULL DEFAULT 'instance_data',
"description" TEXT,
"config" JSONB DEFAULT '{}',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "storage_pools_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "storage_pools_host_id_idx" ON "storage_pools"("host_id");
-- CreateIndex
CREATE UNIQUE INDEX "storage_pools_host_id_name_key" ON "storage_pools"("host_id", "name");
-- AddForeignKey
ALTER TABLE "storage_pools" ADD CONSTRAINT "storage_pools_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,39 @@
-- CreateEnum
CREATE TYPE "ProxySiteStatus" AS ENUM ('pending', 'active', 'error');
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "caddy_enabled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "caddy_password" TEXT,
ADD COLUMN "caddy_port" INTEGER NOT NULL DEFAULT 8444,
ADD COLUMN "caddy_username" TEXT;
-- CreateTable
CREATE TABLE "proxy_sites" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"domain" TEXT NOT NULL,
"target_port" INTEGER NOT NULL,
"status" "ProxySiteStatus" NOT NULL DEFAULT 'pending',
"enabled" BOOLEAN NOT NULL DEFAULT true,
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "proxy_sites_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "proxy_sites_instance_id_idx" ON "proxy_sites"("instance_id");
-- CreateIndex
CREATE INDEX "proxy_sites_host_id_idx" ON "proxy_sites"("host_id");
-- CreateIndex
CREATE UNIQUE INDEX "proxy_sites_host_id_domain_key" ON "proxy_sites"("host_id", "domain");
-- AddForeignKey
ALTER TABLE "proxy_sites" ADD CONSTRAINT "proxy_sites_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "proxy_sites" ADD CONSTRAINT "proxy_sites_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,12 @@
-- AlterTable: 为套餐添加站点数量限制
ALTER TABLE "packages" ADD COLUMN IF NOT EXISTS "site_limit" INTEGER NOT NULL DEFAULT 10;
-- AlterTable: 为实例添加站点数量限制(可覆盖套餐设置)
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "site_limit" INTEGER;
-- 对于已有实例,从套餐继承站点限制
UPDATE "instances" i SET
"site_limit" = COALESCE(i."site_limit", p."site_limit")
FROM "packages" p
WHERE i."package_id" = p."id"
AND i."site_limit" IS NULL;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "proxy_sites" ADD COLUMN "https_enabled" BOOLEAN NOT NULL DEFAULT true;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "user_quotas" ADD COLUMN "package_limit" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "package_used" INTEGER NOT NULL DEFAULT 0,
ALTER COLUMN "host_limit" SET DEFAULT 0,
ALTER COLUMN "friend_limit" SET DEFAULT 0;
@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "global_max_instances" INTEGER,
ADD COLUMN "global_quota_multiplier" DECIMAL(3,1),
ADD COLUMN "global_shared" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "allow_instance_deletion" BOOLEAN NOT NULL DEFAULT true;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "packages" ALTER COLUMN "allow_instance_deletion" SET DEFAULT false;
@@ -0,0 +1,7 @@
-- AlterTable: Add pinned field to help_articles
ALTER TABLE "help_articles" ADD COLUMN "pinned" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable: Remove instance quota fields from user_quotas
ALTER TABLE "user_quotas" DROP COLUMN "instance_limit";
ALTER TABLE "user_quotas" DROP COLUMN "instance_used";
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "ban_reason" TEXT;
@@ -0,0 +1,22 @@
-- CreateTable
CREATE TABLE "inbox_messages" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"event_type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"is_read" BOOLEAN NOT NULL DEFAULT false,
"data" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "inbox_messages_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "inbox_messages_user_id_is_read_idx" ON "inbox_messages"("user_id", "is_read");
-- CreateIndex
CREATE INDEX "inbox_messages_user_id_created_at_idx" ON "inbox_messages"("user_id", "created_at" DESC);
-- AddForeignKey
ALTER TABLE "inbox_messages" ADD CONSTRAINT "inbox_messages_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "packages" ADD COLUMN "release_channel_id" INTEGER;
-- AddForeignKey
ALTER TABLE "packages" ADD CONSTRAINT "packages_release_channel_id_fkey" FOREIGN KEY ("release_channel_id") REFERENCES "notification_channels"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,40 @@
-- CreateEnum
CREATE TYPE "InstanceTaskType" AS ENUM ('start', 'stop', 'restart', 'rebuild', 'clone');
-- CreateEnum
CREATE TYPE "InstanceTaskStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED');
-- CreateTable
CREATE TABLE "instance_tasks" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"task_type" "InstanceTaskType" NOT NULL,
"status" "InstanceTaskStatus" NOT NULL DEFAULT 'PENDING',
"progress" TEXT,
"retry_count" INTEGER NOT NULL DEFAULT 0,
"image_alias" TEXT,
"target_name" TEXT,
"target_host_id" INTEGER,
"snapshot_name" TEXT,
"new_instance_id" INTEGER,
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"started_at" TIMESTAMP(3),
"finished_at" TIMESTAMP(3),
CONSTRAINT "instance_tasks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "instance_tasks_host_id_status_idx" ON "instance_tasks"("host_id", "status");
-- CreateIndex
CREATE INDEX "instance_tasks_instance_id_idx" ON "instance_tasks"("instance_id");
-- CreateIndex
CREATE INDEX "instance_tasks_user_id_idx" ON "instance_tasks"("user_id");
-- AddForeignKey
ALTER TABLE "instance_tasks" ADD CONSTRAINT "instance_tasks_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "proxy_sites" ADD COLUMN "remark" TEXT;
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "login_records" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"ip" TEXT NOT NULL,
"country" TEXT,
"region" TEXT,
"city" TEXT,
"isp" TEXT,
"timezone" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "login_records_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "login_records_user_id_created_at_idx" ON "login_records"("user_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "login_records_user_id_ip_idx" ON "login_records"("user_id", "ip");
-- AddForeignKey
ALTER TABLE "login_records" ADD CONSTRAINT "login_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,11 @@
-- AlterTable
ALTER TABLE "instances" ADD COLUMN "last_synced_at" TIMESTAMP(3);
-- CreateIndex
CREATE INDEX "backup_policies_enabled_next_run_at_idx" ON "backup_policies"("enabled", "next_run_at");
-- CreateIndex
CREATE INDEX "instances_host_id_status_idx" ON "instances"("host_id", "status");
-- CreateIndex
CREATE INDEX "snapshot_policies_enabled_next_run_at_idx" ON "snapshot_policies"("enabled", "next_run_at");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "instance_tasks" ADD COLUMN "ssh_key_id" INTEGER;
@@ -0,0 +1,5 @@
-- AlterEnum: Add 'processing' to TransferStatus
ALTER TYPE "TransferStatus" ADD VALUE 'processing';
-- AlterTable: Add updated_at column to instance_transfers
ALTER TABLE "instance_transfers" ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
@@ -0,0 +1,99 @@
-- CreateEnum
CREATE TYPE "AnnouncementType" AS ENUM ('system_broadcast', 'host_broadcast', 'admin_message', 'host_message');
-- CreateEnum
CREATE TYPE "TicketStatus" AS ENUM ('open', 'in_progress', 'resolved', 'closed');
-- CreateEnum
CREATE TYPE "TicketPriority" AS ENUM ('low', 'normal', 'high', 'urgent');
-- AlterTable
ALTER TABLE "instance_transfers" ALTER COLUMN "updated_at" DROP DEFAULT;
-- CreateTable
CREATE TABLE "announcements" (
"id" SERIAL NOT NULL,
"type" "AnnouncementType" NOT NULL,
"sender_id" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"recipient_count" INTEGER NOT NULL,
"host_id" INTEGER,
"target_user_id" INTEGER,
"instance_id" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "announcements_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tickets" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"instance_id" INTEGER,
"subject" TEXT NOT NULL,
"category" TEXT NOT NULL DEFAULT 'general',
"priority" "TicketPriority" NOT NULL DEFAULT 'normal',
"status" "TicketStatus" NOT NULL DEFAULT 'open',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"resolved_at" TIMESTAMP(3),
"closed_at" TIMESTAMP(3),
CONSTRAINT "tickets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ticket_messages" (
"id" SERIAL NOT NULL,
"ticket_id" INTEGER NOT NULL,
"sender_id" INTEGER NOT NULL,
"content" TEXT NOT NULL,
"is_from_owner" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ticket_messages_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "announcements_sender_id_created_at_idx" ON "announcements"("sender_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "announcements_host_id_created_at_idx" ON "announcements"("host_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "announcements_type_created_at_idx" ON "announcements"("type", "created_at" DESC);
-- CreateIndex
CREATE INDEX "tickets_user_id_status_idx" ON "tickets"("user_id", "status");
-- CreateIndex
CREATE INDEX "tickets_host_id_status_idx" ON "tickets"("host_id", "status");
-- CreateIndex
CREATE INDEX "tickets_status_created_at_idx" ON "tickets"("status", "created_at" DESC);
-- CreateIndex
CREATE INDEX "ticket_messages_ticket_id_created_at_idx" ON "ticket_messages"("ticket_id", "created_at");
-- AddForeignKey
ALTER TABLE "announcements" ADD CONSTRAINT "announcements_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "announcements" ADD CONSTRAINT "announcements_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tickets" ADD CONSTRAINT "tickets_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tickets" ADD CONSTRAINT "tickets_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tickets" ADD CONSTRAINT "tickets_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ticket_messages" ADD CONSTRAINT "ticket_messages_ticket_id_fkey" FOREIGN KEY ("ticket_id") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ticket_messages" ADD CONSTRAINT "ticket_messages_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1 @@
-- This is an empty migration.
@@ -0,0 +1,57 @@
-- CreateEnum
CREATE TYPE "RedeemCodeType" AS ENUM ('c', 'r', 'd', 't');
-- CreateTable
CREATE TABLE "checkin_records" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"redeem_code" TEXT NOT NULL,
"code_type" "RedeemCodeType" NOT NULL,
"code_value" INTEGER NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"used_at" TIMESTAMP(3),
"used_by" INTEGER,
"used_for" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "checkin_records_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "checkin_stats" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"last_checkin_date" TIMESTAMP(3),
"last_redeem_date" TIMESTAMP(3),
"consecutive_others_use" INTEGER NOT NULL DEFAULT 0,
"self_only_mode" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "checkin_stats_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "checkin_records_redeem_code_key" ON "checkin_records"("redeem_code");
-- CreateIndex
CREATE INDEX "checkin_records_user_id_idx" ON "checkin_records"("user_id");
-- CreateIndex
CREATE INDEX "checkin_records_used_by_idx" ON "checkin_records"("used_by");
-- CreateIndex
CREATE INDEX "checkin_records_expires_at_idx" ON "checkin_records"("expires_at");
-- CreateIndex
CREATE UNIQUE INDEX "checkin_stats_user_id_key" ON "checkin_stats"("user_id");
-- AddForeignKey
ALTER TABLE "checkin_records" ADD CONSTRAINT "checkin_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "checkin_records" ADD CONSTRAINT "checkin_records_used_by_fkey" FOREIGN KEY ("used_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "checkin_records" ADD CONSTRAINT "checkin_records_used_for_fkey" FOREIGN KEY ("used_for") REFERENCES "instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "checkin_stats" ADD CONSTRAINT "checkin_stats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,64 @@
-- CreateTable
CREATE TABLE "redeem_codes" (
"id" SERIAL NOT NULL,
"code" TEXT NOT NULL,
"host_id" INTEGER NOT NULL,
"created_by_id" INTEGER NOT NULL,
"code_type" "RedeemCodeType" NOT NULL,
"code_value" INTEGER NOT NULL,
"max_uses" INTEGER NOT NULL DEFAULT 1,
"used_count" INTEGER NOT NULL DEFAULT 0,
"expires_at" TIMESTAMP(3),
"enabled" BOOLEAN NOT NULL DEFAULT true,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "redeem_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "redeem_code_usages" (
"id" SERIAL NOT NULL,
"redeem_code_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"instance_id" INTEGER NOT NULL,
"used_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "redeem_code_usages_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "redeem_codes_code_key" ON "redeem_codes"("code");
-- CreateIndex
CREATE INDEX "redeem_codes_host_id_idx" ON "redeem_codes"("host_id");
-- CreateIndex
CREATE INDEX "redeem_codes_created_by_id_idx" ON "redeem_codes"("created_by_id");
-- CreateIndex
CREATE INDEX "redeem_codes_enabled_idx" ON "redeem_codes"("enabled");
-- CreateIndex
CREATE INDEX "redeem_code_usages_redeem_code_id_idx" ON "redeem_code_usages"("redeem_code_id");
-- CreateIndex
CREATE INDEX "redeem_code_usages_user_id_idx" ON "redeem_code_usages"("user_id");
-- CreateIndex
CREATE INDEX "redeem_code_usages_instance_id_idx" ON "redeem_code_usages"("instance_id");
-- AddForeignKey
ALTER TABLE "redeem_codes" ADD CONSTRAINT "redeem_codes_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "redeem_codes" ADD CONSTRAINT "redeem_codes_created_by_id_fkey" FOREIGN KEY ("created_by_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "redeem_code_usages" ADD CONSTRAINT "redeem_code_usages_redeem_code_id_fkey" FOREIGN KEY ("redeem_code_id") REFERENCES "redeem_codes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "redeem_code_usages" ADD CONSTRAINT "redeem_code_usages_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "redeem_code_usages" ADD CONSTRAINT "redeem_code_usages_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "redeem_codes" ADD COLUMN "batch_id" TEXT;
-- CreateIndex
CREATE INDEX "redeem_codes_batch_id_idx" ON "redeem_codes"("batch_id");
@@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[user_id,batch_id]` on the table `redeem_code_usages` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "redeem_code_usages" ADD COLUMN "batch_id" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "redeem_code_usages_user_id_batch_id_key" ON "redeem_code_usages"("user_id", "batch_id");
@@ -0,0 +1,18 @@
-- AlterEnum: Add 'suspended' to InstanceStatus
ALTER TYPE "InstanceStatus" ADD VALUE 'suspended';
-- AlterTable: Add billing fields to packages
ALTER TABLE "packages" ADD COLUMN "price_monthly" DECIMAL(10,2);
ALTER TABLE "packages" ADD COLUMN "setup_fee" DECIMAL(10,2) NOT NULL DEFAULT 0;
-- AlterTable: Add billing and suspend fields to instances
ALTER TABLE "instances" ADD COLUMN "expires_at" TIMESTAMP(3);
ALTER TABLE "instances" ADD COLUMN "suspended_at" TIMESTAMP(3);
ALTER TABLE "instances" ADD COLUMN "suspended_by" INTEGER;
ALTER TABLE "instances" ADD COLUMN "suspend_reason" TEXT;
ALTER TABLE "instances" ADD COLUMN "billing_price" DECIMAL(10,2);
ALTER TABLE "instances" ADD COLUMN "auto_renew" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "instances" ADD COLUMN "expiry_notified_at" TIMESTAMP(3);
-- CreateIndex: For billing scheduler to find expiring instances
CREATE INDEX "instances_expires_at_idx" ON "instances"("expires_at");
@@ -0,0 +1,26 @@
-- CreateTable
CREATE TABLE "custom_init_commands" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"command" TEXT NOT NULL,
"distros" TEXT NOT NULL,
"description" TEXT,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "custom_init_commands_pkey" PRIMARY KEY ("id")
);
-- AlterTable: Add custom_init_command_ids to instance_tasks
ALTER TABLE "instance_tasks" ADD COLUMN "custom_init_command_ids" TEXT;
-- CreateIndex
CREATE INDEX "custom_init_commands_user_id_idx" ON "custom_init_commands"("user_id");
-- CreateIndex
CREATE INDEX "custom_init_commands_user_id_enabled_idx" ON "custom_init_commands"("user_id", "enabled");
-- AddForeignKey
ALTER TABLE "custom_init_commands" ADD CONSTRAINT "custom_init_commands_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable: 添加 IPv6 重新分配冷却时间字段(每实例每天限一次)
ALTER TABLE "instances" ADD COLUMN "last_ipv6_reassign_at" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "transfer_enabled" BOOLEAN NOT NULL DEFAULT true;
@@ -0,0 +1,209 @@
-- CreateEnum: Balance Log Types
CREATE TYPE "BalanceLogType" AS ENUM ('recharge', 'consume', 'refund', 'admin_adjust', 'gift');
-- CreateEnum: Payment Provider Types
CREATE TYPE "PaymentProviderType" AS ENUM ('yipay', 'stripe', 'alipay_direct', 'wechat_direct', 'manual');
-- CreateEnum: Payment Provider Status
CREATE TYPE "PaymentProviderStatus" AS ENUM ('active', 'disabled', 'testing');
-- CreateEnum: Recharge Status
CREATE TYPE "RechargeStatus" AS ENUM ('pending', 'paid', 'completed', 'failed', 'cancelled', 'refunded');
-- CreateEnum: Billing Record Types
CREATE TYPE "BillingRecordType" AS ENUM ('newPurchase', 'renew', 'upgrade', 'downgrade', 'refund');
-- AlterTable: Add balance field to users
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "balance" DECIMAL(10,2) NOT NULL DEFAULT 0;
-- AlterTable: Add billing fields to instances
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "package_plan_id" INTEGER;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "billing_cycle" INTEGER;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "auto_renew_attempts" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "last_auto_renew_attempt_at" TIMESTAMP(3);
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "version" INTEGER NOT NULL DEFAULT 0;
-- CreateTable: PaymentProvider
CREATE TABLE IF NOT EXISTS "payment_providers" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"type" "PaymentProviderType" NOT NULL,
"status" "PaymentProviderStatus" NOT NULL DEFAULT 'disabled',
"config" JSONB NOT NULL DEFAULT '{}',
"methods" JSONB NOT NULL DEFAULT '[]',
"fee_rate" DECIMAL(5,4) NOT NULL DEFAULT 0,
"fee_fixed" DECIMAL(10,2) NOT NULL DEFAULT 0,
"min_amount" DECIMAL(10,2) NOT NULL DEFAULT 1,
"max_amount" DECIMAL(10,2),
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "payment_providers_pkey" PRIMARY KEY ("id")
);
-- CreateTable: BalanceLog
CREATE TABLE IF NOT EXISTS "balance_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "BalanceLogType" NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"balance_before" DECIMAL(10,2) NOT NULL,
"balance_after" DECIMAL(10,2) NOT NULL,
"order_id" TEXT,
"instance_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "balance_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable: RechargeRecord
CREATE TABLE IF NOT EXISTS "recharge_records" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"provider_id" INTEGER NOT NULL,
"order_no" TEXT NOT NULL,
"trade_no" TEXT,
"amount" DECIMAL(10,2) NOT NULL,
"actual_amount" DECIMAL(10,2),
"fee" DECIMAL(10,2) NOT NULL DEFAULT 0,
"payment_method" TEXT,
"status" "RechargeStatus" NOT NULL DEFAULT 'pending',
"callback_data" JSONB,
"callback_at" TIMESTAMP(3),
"fail_reason" TEXT,
"ip" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"expired_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3),
CONSTRAINT "recharge_records_pkey" PRIMARY KEY ("id")
);
-- CreateTable: PackagePlan
CREATE TABLE IF NOT EXISTS "package_plans" (
"id" SERIAL NOT NULL,
"package_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"cpu" INTEGER NOT NULL,
"memory" INTEGER NOT NULL,
"disk" INTEGER NOT NULL,
"port_limit" INTEGER NOT NULL,
"snapshot_limit" INTEGER NOT NULL,
"backup_limit" INTEGER NOT NULL,
"site_limit" INTEGER NOT NULL,
"traffic_limit" BIGINT NOT NULL,
"traffic_limit_speed" TEXT NOT NULL DEFAULT '1Mbit',
"price" DECIMAL(10,2) NOT NULL,
"billing_cycle" INTEGER NOT NULL DEFAULT 1,
"setup_fee" DECIMAL(10,2) NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"stock_limit" INTEGER,
"stock_used" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "package_plans_pkey" PRIMARY KEY ("id")
);
-- CreateTable: InstanceBillingRecord
CREATE TABLE IF NOT EXISTS "instance_billing_records" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "BillingRecordType" NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"months" INTEGER NOT NULL DEFAULT 1,
"period_start" TIMESTAMP(3) NOT NULL,
"period_end" TIMESTAMP(3) NOT NULL,
"balance_log_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "instance_billing_records_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "recharge_records_order_no_key" ON "recharge_records"("order_no");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "balance_logs_user_id_created_at_idx" ON "balance_logs"("user_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "balance_logs_type_idx" ON "balance_logs"("type");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_user_id_status_idx" ON "recharge_records"("user_id", "status");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_status_created_at_idx" ON "recharge_records"("status", "created_at");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_order_no_idx" ON "recharge_records"("order_no");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_trade_no_idx" ON "recharge_records"("trade_no");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "package_plans_package_id_name_key" ON "package_plans"("package_id", "name");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "package_plans_package_id_is_active_idx" ON "package_plans"("package_id", "is_active");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "instance_billing_records_instance_id_created_at_idx" ON "instance_billing_records"("instance_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "instance_billing_records_user_id_created_at_idx" ON "instance_billing_records"("user_id", "created_at" DESC);
-- AddForeignKey (only if not exists - handle gracefully)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'balance_logs_user_id_fkey') THEN
ALTER TABLE "balance_logs" ADD CONSTRAINT "balance_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'recharge_records_user_id_fkey') THEN
ALTER TABLE "recharge_records" ADD CONSTRAINT "recharge_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'recharge_records_provider_id_fkey') THEN
ALTER TABLE "recharge_records" ADD CONSTRAINT "recharge_records_provider_id_fkey" FOREIGN KEY ("provider_id") REFERENCES "payment_providers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'package_plans_package_id_fkey') THEN
ALTER TABLE "package_plans" ADD CONSTRAINT "package_plans_package_id_fkey" FOREIGN KEY ("package_id") REFERENCES "packages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'instances_package_plan_id_fkey') THEN
ALTER TABLE "instances" ADD CONSTRAINT "instances_package_plan_id_fkey" FOREIGN KEY ("package_plan_id") REFERENCES "package_plans"("id") ON DELETE SET NULL ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'instance_billing_records_instance_id_fkey') THEN
ALTER TABLE "instance_billing_records" ADD CONSTRAINT "instance_billing_records_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- Remove old billing fields from packages (if exist)
ALTER TABLE "packages" DROP COLUMN IF EXISTS "price_monthly";
ALTER TABLE "packages" DROP COLUMN IF EXISTS "setup_fee";
@@ -0,0 +1,9 @@
-- Replace stock_limit/stock_used with sla_guarantee in package_plans table
-- This migration converts the inventory system to an SLA guarantee display
-- Step 1: Add the new sla_guarantee column
ALTER TABLE "package_plans" ADD COLUMN IF NOT EXISTS "sla_guarantee" DECIMAL(5,2);
-- Step 2: Drop the old stock columns (no data migration needed as we're changing the concept entirely)
ALTER TABLE "package_plans" DROP COLUMN IF EXISTS "stock_limit";
ALTER TABLE "package_plans" DROP COLUMN IF EXISTS "stock_used";
@@ -0,0 +1,152 @@
-- CreateEnum: AFF Log Types (if not exists)
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'AffLogType') THEN
CREATE TYPE "AffLogType" AS ENUM ('new_purchase', 'renew', 'convert');
END IF;
END $$;
-- CreateEnum: AFF Withdrawal Status (if not exists)
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'AffWithdrawalStatus') THEN
CREATE TYPE "AffWithdrawalStatus" AS ENUM ('pending', 'approved', 'rejected');
END IF;
END $$;
-- AlterTable: Add AFF fields to users
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "aff_balance" DECIMAL(10,2) NOT NULL DEFAULT 0;
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "aff_activated_at" TIMESTAMP(3);
-- CreateTable: AffCode (优惠码)
CREATE TABLE IF NOT EXISTS "aff_codes" (
"id" SERIAL NOT NULL,
"code" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
"package_plan_id" INTEGER NOT NULL,
"discount_rate" DECIMAL(5,4) NOT NULL DEFAULT 0.05,
"commission_rate" DECIMAL(5,4) NOT NULL DEFAULT 0.05,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"used_count" INTEGER NOT NULL DEFAULT 0,
"total_earnings" DECIMAL(10,2) NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "aff_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable: AffBinding (实例与优惠码绑定)
CREATE TABLE IF NOT EXISTS "aff_bindings" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"aff_code_id" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "aff_bindings_pkey" PRIMARY KEY ("id")
);
-- CreateTable: AffLog (AFF余额变动日志)
CREATE TABLE IF NOT EXISTS "aff_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"aff_code_id" INTEGER,
"instance_id" INTEGER,
"type" "AffLogType" NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"original_amount" DECIMAL(10,2),
"balance_before" DECIMAL(10,2) NOT NULL,
"balance_after" DECIMAL(10,2) NOT NULL,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "aff_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable: AffWithdrawal (AFF余额转化申请)
CREATE TABLE IF NOT EXISTS "aff_withdrawals" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"status" "AffWithdrawalStatus" NOT NULL DEFAULT 'pending',
"reject_reason" TEXT,
"reviewed_by" INTEGER,
"reviewed_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "aff_withdrawals_pkey" PRIMARY KEY ("id")
);
-- CreateIndex: Unique code
CREATE UNIQUE INDEX IF NOT EXISTS "aff_codes_code_key" ON "aff_codes"("code");
-- CreateIndex: User can only have one code per plan
CREATE UNIQUE INDEX IF NOT EXISTS "aff_codes_user_id_package_plan_id_key" ON "aff_codes"("user_id", "package_plan_id");
-- CreateIndex: AffCode indexes
CREATE INDEX IF NOT EXISTS "aff_codes_user_id_idx" ON "aff_codes"("user_id");
CREATE INDEX IF NOT EXISTS "aff_codes_package_plan_id_idx" ON "aff_codes"("package_plan_id");
-- CreateIndex: AffBinding - one instance can only bind one code
CREATE UNIQUE INDEX IF NOT EXISTS "aff_bindings_instance_id_key" ON "aff_bindings"("instance_id");
CREATE INDEX IF NOT EXISTS "aff_bindings_aff_code_id_idx" ON "aff_bindings"("aff_code_id");
-- CreateIndex: AffLog indexes
CREATE INDEX IF NOT EXISTS "aff_logs_user_id_created_at_idx" ON "aff_logs"("user_id", "created_at" DESC);
CREATE INDEX IF NOT EXISTS "aff_logs_aff_code_id_idx" ON "aff_logs"("aff_code_id");
-- CreateIndex: AffWithdrawal indexes
CREATE INDEX IF NOT EXISTS "aff_withdrawals_user_id_status_idx" ON "aff_withdrawals"("user_id", "status");
CREATE INDEX IF NOT EXISTS "aff_withdrawals_status_created_at_idx" ON "aff_withdrawals"("status", "created_at");
-- AddForeignKey: AffCode -> User
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_codes_user_id_fkey') THEN
ALTER TABLE "aff_codes" ADD CONSTRAINT "aff_codes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffCode -> PackagePlan
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_codes_package_plan_id_fkey') THEN
ALTER TABLE "aff_codes" ADD CONSTRAINT "aff_codes_package_plan_id_fkey" FOREIGN KEY ("package_plan_id") REFERENCES "package_plans"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffBinding -> Instance
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_bindings_instance_id_fkey') THEN
ALTER TABLE "aff_bindings" ADD CONSTRAINT "aff_bindings_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffBinding -> AffCode
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_bindings_aff_code_id_fkey') THEN
ALTER TABLE "aff_bindings" ADD CONSTRAINT "aff_bindings_aff_code_id_fkey" FOREIGN KEY ("aff_code_id") REFERENCES "aff_codes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffLog -> User
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_logs_user_id_fkey') THEN
ALTER TABLE "aff_logs" ADD CONSTRAINT "aff_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffLog -> AffCode (SetNull on delete)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_logs_aff_code_id_fkey') THEN
ALTER TABLE "aff_logs" ADD CONSTRAINT "aff_logs_aff_code_id_fkey" FOREIGN KEY ("aff_code_id") REFERENCES "aff_codes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
END IF;
END $$;
-- AddForeignKey: AffWithdrawal -> User
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'aff_withdrawals_user_id_fkey') THEN
ALTER TABLE "aff_withdrawals" ADD CONSTRAINT "aff_withdrawals_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
@@ -0,0 +1,18 @@
-- CreateTable: 支付回调防重放记录
CREATE TABLE IF NOT EXISTS "payment_callbacks" (
"id" SERIAL NOT NULL,
"provider_id" INTEGER NOT NULL,
"order_no" TEXT NOT NULL,
"trade_no" TEXT,
"callback_ip" TEXT,
"processed" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "payment_callbacks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex: 联合唯一索引防重放
CREATE UNIQUE INDEX IF NOT EXISTS "payment_callbacks_provider_id_order_no_trade_no_key" ON "payment_callbacks"("provider_id", "order_no", "trade_no");
-- CreateIndex: 用于定期清理
CREATE INDEX IF NOT EXISTS "payment_callbacks_created_at_idx" ON "payment_callbacks"("created_at");
@@ -0,0 +1,5 @@
-- AlterTable: Make host_id optional in tickets table
-- This allows users to create tickets without selecting an instance
-- (e.g., for billing/recharge issues before having any instances)
ALTER TABLE "tickets" ALTER COLUMN "host_id" DROP NOT NULL;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "instances" ADD COLUMN "last_plan_change_at" TIMESTAMP(3);
@@ -0,0 +1,18 @@
-- AlterTable: Make package_plan_id nullable to support global AFF codes
-- Global AFF codes have package_plan_id = NULL and can be used for all paid plans
-- Step 1: Drop the existing foreign key constraint
ALTER TABLE "aff_codes" DROP CONSTRAINT IF EXISTS "aff_codes_package_plan_id_fkey";
-- Step 2: Alter column to be nullable
ALTER TABLE "aff_codes" ALTER COLUMN "package_plan_id" DROP NOT NULL;
-- Step 3: Re-add foreign key with ON DELETE CASCADE (allows NULL values)
ALTER TABLE "aff_codes" ADD CONSTRAINT "aff_codes_package_plan_id_fkey"
FOREIGN KEY ("package_plan_id") REFERENCES "package_plans"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
-- Note: The unique constraint on (user_id, package_plan_id) remains
-- In PostgreSQL, NULL values are not considered equal for unique constraints,
-- so multiple rows with (same_user_id, NULL) would be allowed by the database.
-- The application layer uses transactions to prevent duplicate global codes per user.
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "traffic_reset_day" INTEGER NOT NULL DEFAULT 1;
@@ -0,0 +1,145 @@
-- CreateEnum
CREATE TYPE "PointsLogType" AS ENUM ('convert', 'lottery_win', 'lottery_spend', 'admin_adjust');
-- CreateEnum
CREATE TYPE "LotteryPrizeType" AS ENUM ('nothing', 'points', 'balance', 'instance');
-- CreateEnum
CREATE TYPE "LotteryRecordStatus" AS ENUM ('pending', 'delivered', 'claimed', 'expired');
-- CreateTable
CREATE TABLE "user_points" (
"user_id" INTEGER NOT NULL,
"points" INTEGER NOT NULL DEFAULT 0,
"total_earned" INTEGER NOT NULL DEFAULT 0,
"total_spent" INTEGER NOT NULL DEFAULT 0,
"converted_consume" DECIMAL(10,2) NOT NULL DEFAULT 0,
"last_converted_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_points_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "points_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "PointsLogType" NOT NULL,
"amount" INTEGER NOT NULL,
"points_before" INTEGER NOT NULL,
"points_after" INTEGER NOT NULL,
"related_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "points_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "lotteries" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"cost_points" INTEGER NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"start_at" TIMESTAMP(3),
"end_at" TIMESTAMP(3),
"total_draws" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" INTEGER NOT NULL,
CONSTRAINT "lotteries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "lottery_prizes" (
"id" SERIAL NOT NULL,
"lottery_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"type" "LotteryPrizeType" NOT NULL,
"value" INTEGER NOT NULL DEFAULT 0,
"probability" DECIMAL(5,2) NOT NULL,
"total_quantity" INTEGER,
"remain_quantity" INTEGER,
"display_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"instance_desc" TEXT,
CONSTRAINT "lottery_prizes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "lottery_records" (
"id" SERIAL NOT NULL,
"lottery_id" INTEGER NOT NULL,
"prize_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"prize_type" "LotteryPrizeType" NOT NULL,
"prize_value" INTEGER NOT NULL,
"prize_name" TEXT NOT NULL,
"status" "LotteryRecordStatus" NOT NULL DEFAULT 'delivered',
"points_spent" INTEGER NOT NULL,
"delivered_at" TIMESTAMP(3),
"delivered_by" INTEGER,
"ticket_id" INTEGER,
"notification_sent" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "lottery_records_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "lottery_notification_configs" (
"id" SERIAL NOT NULL,
"lottery_id" INTEGER NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"type" TEXT NOT NULL,
"config" JSONB NOT NULL,
"notify_balance" BOOLEAN NOT NULL DEFAULT true,
"notify_instance" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "lottery_notification_configs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "points_logs_user_id_created_at_idx" ON "points_logs"("user_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "lottery_prizes_lottery_id_idx" ON "lottery_prizes"("lottery_id");
-- CreateIndex
CREATE INDEX "lottery_records_user_id_created_at_idx" ON "lottery_records"("user_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "lottery_records_lottery_id_idx" ON "lottery_records"("lottery_id");
-- CreateIndex
CREATE INDEX "lottery_records_status_idx" ON "lottery_records"("status");
-- CreateIndex
CREATE UNIQUE INDEX "lottery_notification_configs_lottery_id_key" ON "lottery_notification_configs"("lottery_id");
-- AddForeignKey
ALTER TABLE "user_points" ADD CONSTRAINT "user_points_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "points_logs" ADD CONSTRAINT "points_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lottery_prizes" ADD CONSTRAINT "lottery_prizes_lottery_id_fkey" FOREIGN KEY ("lottery_id") REFERENCES "lotteries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lottery_records" ADD CONSTRAINT "lottery_records_lottery_id_fkey" FOREIGN KEY ("lottery_id") REFERENCES "lotteries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lottery_records" ADD CONSTRAINT "lottery_records_prize_id_fkey" FOREIGN KEY ("prize_id") REFERENCES "lottery_prizes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lottery_records" ADD CONSTRAINT "lottery_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "lottery_notification_configs" ADD CONSTRAINT "lottery_notification_configs_lottery_id_fkey" FOREIGN KEY ("lottery_id") REFERENCES "lotteries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- Add 'checkin' value to PointsLogType enum
ALTER TYPE "PointsLogType" ADD VALUE 'checkin';
@@ -0,0 +1,2 @@
-- AlterEnum: Add 'recreate' to InstanceTaskType
ALTER TYPE "InstanceTaskType" ADD VALUE 'recreate';
@@ -0,0 +1,2 @@
-- AlterEnum: Add 'recreate_instance' to OperationType
ALTER TYPE "OperationType" ADD VALUE 'recreate_instance';
@@ -0,0 +1,6 @@
-- Add transfer fee field to instance_transfers table
ALTER TABLE "instance_transfers" ADD COLUMN "fee" DECIMAL(10,2);
-- Add transfer_fee and transfer_refund to BalanceLogType enum
ALTER TYPE "BalanceLogType" ADD VALUE IF NOT EXISTS 'transfer_fee';
ALTER TYPE "BalanceLogType" ADD VALUE IF NOT EXISTS 'transfer_refund';
@@ -0,0 +1,3 @@
-- Add transfer_fee to BillingRecordType enum
-- This is safe: adding a new enum value does not affect existing records
ALTER TYPE "BillingRecordType" ADD VALUE IF NOT EXISTS 'transfer_fee';
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "announcement" TEXT;
@@ -0,0 +1,47 @@
-- CreateEnum
CREATE TYPE "ResourcePoolAction" AS ENUM ('checkin', 'redeem', 'admin_grant', 'system_grant', 'lottery', 'apply');
-- CreateTable
CREATE TABLE "user_resource_pools" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"cpu" INTEGER NOT NULL DEFAULT 0,
"memory" INTEGER NOT NULL DEFAULT 0,
"disk" INTEGER NOT NULL DEFAULT 0,
"traffic" BIGINT NOT NULL DEFAULT 0,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_resource_pools_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "resource_pool_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"action" "ResourcePoolAction" NOT NULL,
"resource_type" "RedeemCodeType" NOT NULL,
"amount" INTEGER NOT NULL,
"instance_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "resource_pool_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "user_resource_pools_user_id_key" ON "user_resource_pools"("user_id");
-- CreateIndex
CREATE INDEX "resource_pool_logs_user_id_idx" ON "resource_pool_logs"("user_id");
-- CreateIndex
CREATE INDEX "resource_pool_logs_user_id_action_idx" ON "resource_pool_logs"("user_id", "action");
-- AddForeignKey
ALTER TABLE "user_resource_pools" ADD CONSTRAINT "user_resource_pools_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "resource_pool_logs" ADD CONSTRAINT "resource_pool_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "resource_pool_logs" ADD CONSTRAINT "resource_pool_logs_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
-- AlterEnum: Add resource prize types to LotteryPrizeType
ALTER TYPE "LotteryPrizeType" ADD VALUE 'cpu';
ALTER TYPE "LotteryPrizeType" ADD VALUE 'memory';
ALTER TYPE "LotteryPrizeType" ADD VALUE 'disk';
ALTER TYPE "LotteryPrizeType" ADD VALUE 'traffic';
-- AlterEnum: Add system_redeem to ResourcePoolAction
ALTER TYPE "ResourcePoolAction" ADD VALUE 'system_redeem';
@@ -0,0 +1,29 @@
-- CreateTable
CREATE TABLE "user_destroy_records" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"host_id" INTEGER NOT NULL,
"instance_id" INTEGER NOT NULL,
"instance_name" TEXT NOT NULL,
"destroyed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"refund_amount" DECIMAL(10,2) NOT NULL,
"fee_amount" DECIMAL(10,2) NOT NULL,
"is_first_time" BOOLEAN NOT NULL,
CONSTRAINT "user_destroy_records_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "user_destroy_records_user_id_idx" ON "user_destroy_records"("user_id");
-- CreateIndex
CREATE INDEX "user_destroy_records_user_id_host_id_idx" ON "user_destroy_records"("user_id", "host_id");
-- AddForeignKey
ALTER TABLE "user_destroy_records" ADD CONSTRAINT "user_destroy_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_destroy_records" ADD CONSTRAINT "user_destroy_records_host_id_fkey" FOREIGN KEY ("host_id") REFERENCES "hosts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_destroy_records" ADD CONSTRAINT "user_destroy_records_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,53 @@
-- 托管余额系统
-- 允许满足条件的用户自行托管节点并获得收益
-- 枚举类型
CREATE TYPE "HostingBalanceType" AS ENUM ('income', 'unfreeze', 'withdraw');
CREATE TYPE "WithdrawalStatus" AS ENUM ('pending', 'approved', 'rejected', 'completed');
CREATE TYPE "WithdrawalTarget" AS ENUM ('balance', 'usdt');
-- 在 users 表添加托管余额字段
ALTER TABLE "users" ADD COLUMN "hosting_balance" DECIMAL(10, 2) NOT NULL DEFAULT 0;
-- 托管余额变动日志表
CREATE TABLE "hosting_balance_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "HostingBalanceType" NOT NULL,
"amount" DECIMAL(10, 2) NOT NULL,
"frozen" BOOLEAN NOT NULL DEFAULT true,
"unfreeze_at" TIMESTAMP(3),
"related_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "hosting_balance_logs_pkey" PRIMARY KEY ("id")
);
-- 托管余额提现记录表
CREATE TABLE "hosting_withdrawals" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"amount" DECIMAL(10, 2) NOT NULL,
"fee_rate" DECIMAL(5, 4) NOT NULL,
"fee_amount" DECIMAL(10, 2) NOT NULL,
"actual_amount" DECIMAL(10, 2) NOT NULL,
"target" "WithdrawalTarget" NOT NULL,
"usdt_address" TEXT,
"status" "WithdrawalStatus" NOT NULL DEFAULT 'pending',
"reject_reason" TEXT,
"processed_at" TIMESTAMP(3),
"processed_by" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "hosting_withdrawals_pkey" PRIMARY KEY ("id")
);
-- 索引
CREATE INDEX "hosting_balance_logs_user_id_frozen_idx" ON "hosting_balance_logs"("user_id", "frozen");
CREATE INDEX "hosting_balance_logs_unfreeze_at_idx" ON "hosting_balance_logs"("unfreeze_at");
CREATE INDEX "hosting_withdrawals_user_id_status_idx" ON "hosting_withdrawals"("user_id", "status");
-- 外键约束
ALTER TABLE "hosting_balance_logs" ADD CONSTRAINT "hosting_balance_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "hosting_withdrawals" ADD CONSTRAINT "hosting_withdrawals_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- Add hosting_deduction to BalanceLogType enum
ALTER TYPE "BalanceLogType" ADD VALUE IF NOT EXISTS 'hosting_deduction';
@@ -0,0 +1,2 @@
-- Add deduction to HostingBalanceType enum
ALTER TYPE "HostingBalanceType" ADD VALUE IF NOT EXISTS 'deduction';
@@ -0,0 +1,13 @@
-- Add HostingActionType enum and action_type column to hosting_balance_logs
-- Create HostingActionType enum
CREATE TYPE "HostingActionType" AS ENUM ('purchase', 'renew', 'upgrade', 'destroy', 'unfreeze', 'withdraw');
-- Add action_type column (nullable for existing records)
ALTER TABLE "hosting_balance_logs" ADD COLUMN "action_type" "HostingActionType";
-- Backfill action_type for existing records based on type
UPDATE "hosting_balance_logs" SET "action_type" = 'purchase' WHERE "type" = 'income' AND "action_type" IS NULL;
UPDATE "hosting_balance_logs" SET "action_type" = 'unfreeze' WHERE "type" = 'unfreeze' AND "action_type" IS NULL;
UPDATE "hosting_balance_logs" SET "action_type" = 'destroy' WHERE "type" = 'deduction' AND "action_type" IS NULL;
UPDATE "hosting_balance_logs" SET "action_type" = 'withdraw' WHERE "type" = 'withdraw' AND "action_type" IS NULL;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "enable_resource_pool" BOOLEAN NOT NULL DEFAULT true;
@@ -0,0 +1,4 @@
-- 为 BalanceLogType 枚举添加 hosting_withdraw 值
-- 用于记录托管余额提现到面板余额的操作
ALTER TYPE "BalanceLogType" ADD VALUE IF NOT EXISTS 'hosting_withdraw';
@@ -0,0 +1,4 @@
-- 为 HostingActionType 枚举添加 admin_adjust 值
-- 用于标识管理员调整托管余额的操作
ALTER TYPE "HostingActionType" ADD VALUE IF NOT EXISTS 'admin_adjust';
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "hosts" ADD COLUMN "probe_url" TEXT;
@@ -0,0 +1,112 @@
-- 域名邮箱模块
-- 包含邮箱源、方案、订阅、域名、账户管理
-- 创建枚举类型
CREATE TYPE "MailDomainStatus" AS ENUM ('pending', 'verified', 'suspended');
CREATE TYPE "MailSubscriptionStatus" AS ENUM ('active', 'expired', 'suspended');
CREATE TYPE "MailBillingCycle" AS ENUM ('monthly', 'yearly');
-- 邮箱源表(数据中心)
CREATE TABLE "mail_sources" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"code" TEXT NOT NULL,
"api_url" TEXT NOT NULL,
"api_key" TEXT NOT NULL,
"smartermail_url" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "mail_sources_pkey" PRIMARY KEY ("id")
);
-- 套餐方案表
CREATE TABLE "mail_plans" (
"id" SERIAL NOT NULL,
"source_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"domain_limit" INTEGER NOT NULL,
"disk_limit_gb" INTEGER NOT NULL,
"billing_cycle" "MailBillingCycle" NOT NULL,
"price" DECIMAL(10,2) NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "mail_plans_pkey" PRIMARY KEY ("id")
);
-- 用户订阅表
CREATE TABLE "mail_subscriptions" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"source_id" INTEGER NOT NULL,
"plan_id" INTEGER NOT NULL,
"status" "MailSubscriptionStatus" NOT NULL DEFAULT 'active',
"domain_limit" INTEGER NOT NULL,
"disk_limit_gb" INTEGER NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"auto_renew" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "mail_subscriptions_pkey" PRIMARY KEY ("id")
);
-- 域名表
CREATE TABLE "mail_domains" (
"id" SERIAL NOT NULL,
"subscription_id" INTEGER NOT NULL,
"source_id" INTEGER NOT NULL,
"domain" TEXT NOT NULL,
"status" "MailDomainStatus" NOT NULL DEFAULT 'pending',
"admin_username" TEXT,
"admin_password" TEXT,
"disk_used_mb" INTEGER NOT NULL DEFAULT 0,
"verified_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "mail_domains_pkey" PRIMARY KEY ("id")
);
-- 邮箱账户表
CREATE TABLE "mail_accounts" (
"id" SERIAL NOT NULL,
"domain_id" INTEGER NOT NULL,
"email" TEXT NOT NULL,
"username" TEXT NOT NULL,
"display_name" TEXT,
"disk_limit_mb" INTEGER NOT NULL DEFAULT 2048,
"disk_used_mb" INTEGER NOT NULL DEFAULT 0,
"is_admin" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "mail_accounts_pkey" PRIMARY KEY ("id")
);
-- 创建唯一索引
CREATE UNIQUE INDEX "mail_sources_code_key" ON "mail_sources"("code");
CREATE UNIQUE INDEX "mail_domains_domain_source_id_key" ON "mail_domains"("domain", "source_id");
CREATE UNIQUE INDEX "mail_accounts_domain_id_username_key" ON "mail_accounts"("domain_id", "username");
-- 创建普通索引
CREATE INDEX "mail_plans_source_id_enabled_idx" ON "mail_plans"("source_id", "enabled");
CREATE INDEX "mail_subscriptions_user_id_status_idx" ON "mail_subscriptions"("user_id", "status");
CREATE INDEX "mail_subscriptions_expires_at_idx" ON "mail_subscriptions"("expires_at");
CREATE INDEX "mail_domains_subscription_id_idx" ON "mail_domains"("subscription_id");
CREATE INDEX "mail_accounts_domain_id_idx" ON "mail_accounts"("domain_id");
-- 添加外键约束
ALTER TABLE "mail_plans" ADD CONSTRAINT "mail_plans_source_id_fkey" FOREIGN KEY ("source_id") REFERENCES "mail_sources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_subscriptions" ADD CONSTRAINT "mail_subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_subscriptions" ADD CONSTRAINT "mail_subscriptions_source_id_fkey" FOREIGN KEY ("source_id") REFERENCES "mail_sources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_subscriptions" ADD CONSTRAINT "mail_subscriptions_plan_id_fkey" FOREIGN KEY ("plan_id") REFERENCES "mail_plans"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_domains" ADD CONSTRAINT "mail_domains_subscription_id_fkey" FOREIGN KEY ("subscription_id") REFERENCES "mail_subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_domains" ADD CONSTRAINT "mail_domains_source_id_fkey" FOREIGN KEY ("source_id") REFERENCES "mail_sources"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "mail_accounts" ADD CONSTRAINT "mail_accounts_domain_id_fkey" FOREIGN KEY ("domain_id") REFERENCES "mail_domains"("id") ON DELETE CASCADE ON UPDATE CASCADE;

Some files were not shown because too many files have changed in this diff Show More