Add project management features and enhance database schema

- Introduced a new `server_projects` table to manage project details.
- Updated the `vps` table to include a foreign key reference to `server_projects`.
- Enhanced the API to support project-related data retrieval and manipulation.
- Implemented project filtering in the Reports and VPS pages.
- Added project selection functionality in the UI for better resource management.
- Improved data seeding and migration scripts to accommodate new project structure.
This commit is contained in:
Denozordec
2026-03-20 22:50:57 +07:00
parent 7ce754ca90
commit 9d5b434bea
19 changed files with 1298 additions and 77 deletions
+3
View File
@@ -51,6 +51,9 @@ export async function initDb() {
}
dbInstance = db
if (existsSync(DB_PATH)) {
saveDb()
}
return db
}
+58
View File
@@ -2,6 +2,8 @@
* Database migrations — add columns to existing tables
*/
import { randomUUID } from 'node:crypto'
export const MIGRATIONS = [
{
name: 'provider_accounts_api',
@@ -173,4 +175,60 @@ export const MIGRATIONS = [
}
},
},
{
name: 'server_projects',
run(db) {
db.run(
`CREATE TABLE IF NOT EXISTS server_projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
color TEXT,
sortOrder INTEGER DEFAULT 0,
notes TEXT,
createdAt TEXT
)`,
)
try {
db.run('ALTER TABLE vps ADD COLUMN projectId TEXT')
} catch (e) {
if (!String(e.message || e).includes('duplicate column')) throw e
}
const distinctStmt = db.prepare(
`SELECT DISTINCT trim(project) AS n FROM vps WHERE length(trim(COALESCE(project, ''))) > 0`,
)
const seenLower = new Set()
const findStmt = db.prepare(
'SELECT id FROM server_projects WHERE LOWER(name) = LOWER(?) LIMIT 1',
)
while (distinctStmt.step()) {
const row = distinctStmt.getAsObject()
const t = String(row.n ?? '').trim()
if (!t) continue
const lk = t.toLowerCase()
if (seenLower.has(lk)) continue
seenLower.add(lk)
findStmt.bind([t])
const exists = Boolean(findStmt.step())
findStmt.reset()
if (!exists) {
const id = `proj-${randomUUID()}`
const now = new Date().toISOString()
db.run(
`INSERT INTO server_projects (id, name, color, sortOrder, notes, createdAt) VALUES (?, ?, NULL, 0, NULL, ?)`,
[id, t, now],
)
}
}
distinctStmt.free()
findStmt.free()
db.run(`UPDATE vps SET projectId = (
SELECT sp.id FROM server_projects sp
WHERE LOWER(sp.name) = LOWER(trim(COALESCE(vps.project, '')))
LIMIT 1
) WHERE length(trim(COALESCE(vps.project, ''))) > 0`)
},
},
]
+12 -1
View File
@@ -28,6 +28,15 @@ CREATE TABLE IF NOT EXISTS provider_accounts (
FOREIGN KEY (providerId) REFERENCES providers(id)
);
CREATE TABLE IF NOT EXISTS server_projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
color TEXT,
sortOrder INTEGER DEFAULT 0,
notes TEXT,
createdAt TEXT
);
CREATE TABLE IF NOT EXISTS vps (
id TEXT PRIMARY KEY,
ip TEXT,
@@ -51,6 +60,7 @@ CREATE TABLE IF NOT EXISTS vps (
purpose TEXT,
environment TEXT,
project TEXT,
projectId TEXT,
monitoringEnabled INTEGER,
backupEnabled INTEGER,
status TEXT,
@@ -63,7 +73,8 @@ CREATE TABLE IF NOT EXISTS vps (
notes TEXT,
userOverrides TEXT,
FOREIGN KEY (providerId) REFERENCES providers(id),
FOREIGN KEY (providerAccountId) REFERENCES provider_accounts(id)
FOREIGN KEY (providerAccountId) REFERENCES provider_accounts(id),
FOREIGN KEY (projectId) REFERENCES server_projects(id)
);
CREATE TABLE IF NOT EXISTS payments (
+3 -1
View File
@@ -50,7 +50,7 @@ export function seed(db, seedDir) {
const dailyRate = r.dailyRate === '' || r.dailyRate == null ? null : Number(r.dailyRate)
const monthlyRate = r.monthlyRate === '' || r.monthlyRate == null ? null : Number(r.monthlyRate)
run(
`INSERT OR IGNORE INTO vps (id, ip, ipv6, additionalIps, dns, providerId, providerAccountId, country, city, datacenter, os, vcpu, ramGb, diskGb, diskType, virtualization, bandwidthTb, sshPort, rootUser, purpose, environment, project, monitoringEnabled, backupEnabled, status, tariffType, currency, dailyRate, monthlyRate, createdAt, paidUntil, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
`INSERT OR IGNORE INTO vps (id, ip, ipv6, additionalIps, dns, providerId, providerAccountId, country, city, datacenter, os, vcpu, ramGb, diskGb, diskType, virtualization, bandwidthTb, sshPort, rootUser, purpose, environment, project, projectId, monitoringEnabled, backupEnabled, status, tariffType, currency, dailyRate, monthlyRate, createdAt, paidUntil, notes, userOverrides) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
r.id,
r.ip ?? '',
@@ -74,6 +74,7 @@ export function seed(db, seedDir) {
r.purpose ?? '',
r.environment ?? '',
r.project ?? '',
r.projectId ?? null,
r.monitoringEnabled ? 1 : 0,
r.backupEnabled ? 1 : 0,
r.status ?? 'active',
@@ -84,6 +85,7 @@ export function seed(db, seedDir) {
r.createdAt ?? '',
r.paidUntil ?? '',
r.notes ?? '',
'[]',
],
)
}