@pgplex/pgconsole 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -31
- package/dist/client/assets/{index-CzWqr-JX.js → index-50FoceLI.js} +85 -13
- package/dist/client/assets/index-DUHQjBL7.css +2 -0
- package/dist/client/assets/{sql-B7ndC_V7.js → sql-CzmdoutZ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server.mjs +329 -35
- package/dist/server.mjs.map +4 -4
- package/package.json +4 -1
- package/dist/client/assets/index-C-1OZ2hX.css +0 -2
package/dist/server.mjs
CHANGED
|
@@ -41051,10 +41051,12 @@ function requiredPlan(name) {
|
|
|
41051
41051
|
var validSslModes = ["disable", "prefer", "require", "verify-full"];
|
|
41052
41052
|
var DEFAULT_CONFIG = { users: [], groups: [], labels: [], connections: [], auth: void 0, ai: void 0, banner: void 0, license: void 0, iam: [], plan: "FREE", licenseExpiry: void 0, licenseMaxUsers: 1, licenseEmail: void 0, stripeCustomerId: void 0 };
|
|
41053
41053
|
var loadedConfig = { ...DEFAULT_CONFIG };
|
|
41054
|
+
var demoMode = false;
|
|
41054
41055
|
function isValidHexColor(color) {
|
|
41055
41056
|
return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color);
|
|
41056
41057
|
}
|
|
41057
41058
|
async function loadConfig(configPath) {
|
|
41059
|
+
demoMode = false;
|
|
41058
41060
|
const path5 = configPath || "pgconsole.toml";
|
|
41059
41061
|
if (!existsSync(path5)) {
|
|
41060
41062
|
throw new Error(`Config file not found: ${path5}
|
|
@@ -41496,6 +41498,24 @@ function getLicenseEmail() {
|
|
|
41496
41498
|
function getStripeCustomerId() {
|
|
41497
41499
|
return loadedConfig.stripeCustomerId;
|
|
41498
41500
|
}
|
|
41501
|
+
function loadDemoConfig(port) {
|
|
41502
|
+
demoMode = true;
|
|
41503
|
+
loadedConfig = {
|
|
41504
|
+
...DEFAULT_CONFIG,
|
|
41505
|
+
connections: [{
|
|
41506
|
+
id: "demo",
|
|
41507
|
+
name: "Demo Database",
|
|
41508
|
+
host: "127.0.0.1",
|
|
41509
|
+
port,
|
|
41510
|
+
database: "postgres",
|
|
41511
|
+
username: "postgres",
|
|
41512
|
+
ssl_mode: "disable"
|
|
41513
|
+
}]
|
|
41514
|
+
};
|
|
41515
|
+
}
|
|
41516
|
+
function isDemoMode() {
|
|
41517
|
+
return demoMode;
|
|
41518
|
+
}
|
|
41499
41519
|
|
|
41500
41520
|
// server/lib/auth.ts
|
|
41501
41521
|
var DEFAULT_SIGNIN_EXPIRY = "7d";
|
|
@@ -54813,6 +54833,10 @@ var GetConstraintsResponse = class _GetConstraintsResponse extends Message {
|
|
|
54813
54833
|
* @generated from field: repeated query.v1.ConstraintInfo constraints = 1;
|
|
54814
54834
|
*/
|
|
54815
54835
|
constraints = [];
|
|
54836
|
+
/**
|
|
54837
|
+
* @generated from field: repeated query.v1.ConstraintInfo referenced_by = 2;
|
|
54838
|
+
*/
|
|
54839
|
+
referencedBy = [];
|
|
54816
54840
|
constructor(data) {
|
|
54817
54841
|
super();
|
|
54818
54842
|
proto3.util.initPartial(data, this);
|
|
@@ -54820,7 +54844,8 @@ var GetConstraintsResponse = class _GetConstraintsResponse extends Message {
|
|
|
54820
54844
|
static runtime = proto3;
|
|
54821
54845
|
static typeName = "query.v1.GetConstraintsResponse";
|
|
54822
54846
|
static fields = proto3.util.newFieldList(() => [
|
|
54823
|
-
{ no: 1, name: "constraints", kind: "message", T: ConstraintInfo, repeated: true }
|
|
54847
|
+
{ no: 1, name: "constraints", kind: "message", T: ConstraintInfo, repeated: true },
|
|
54848
|
+
{ no: 2, name: "referenced_by", kind: "message", T: ConstraintInfo, repeated: true }
|
|
54824
54849
|
]);
|
|
54825
54850
|
static fromBinary(bytes, options) {
|
|
54826
54851
|
return new _GetConstraintsResponse().fromBinary(bytes, options);
|
|
@@ -57424,6 +57449,12 @@ var AssessChangeRiskResponse = class _AssessChangeRiskResponse extends Message {
|
|
|
57424
57449
|
* @generated from field: string error = 3;
|
|
57425
57450
|
*/
|
|
57426
57451
|
error = "";
|
|
57452
|
+
/**
|
|
57453
|
+
* Mermaid diagram of object dependencies
|
|
57454
|
+
*
|
|
57455
|
+
* @generated from field: string dependency_graph = 4;
|
|
57456
|
+
*/
|
|
57457
|
+
dependencyGraph = "";
|
|
57427
57458
|
constructor(data) {
|
|
57428
57459
|
super();
|
|
57429
57460
|
proto3.util.initPartial(data, this);
|
|
@@ -57445,6 +57476,13 @@ var AssessChangeRiskResponse = class _AssessChangeRiskResponse extends Message {
|
|
|
57445
57476
|
kind: "scalar",
|
|
57446
57477
|
T: 9
|
|
57447
57478
|
/* ScalarType.STRING */
|
|
57479
|
+
},
|
|
57480
|
+
{
|
|
57481
|
+
no: 4,
|
|
57482
|
+
name: "dependency_graph",
|
|
57483
|
+
kind: "scalar",
|
|
57484
|
+
T: 9
|
|
57485
|
+
/* ScalarType.STRING */
|
|
57448
57486
|
}
|
|
57449
57487
|
]);
|
|
57450
57488
|
static fromBinary(bytes, options) {
|
|
@@ -57549,6 +57587,8 @@ function createClient(details, appUser) {
|
|
|
57549
57587
|
ssl: details.sslMode === "disable" ? false : details.sslMode,
|
|
57550
57588
|
connect_timeout: 10,
|
|
57551
57589
|
max: 1,
|
|
57590
|
+
onnotice: () => {
|
|
57591
|
+
},
|
|
57552
57592
|
connection: {
|
|
57553
57593
|
application_name: formatAppName(appUser),
|
|
57554
57594
|
...details.lockTimeout && { lock_timeout: details.lockTimeout },
|
|
@@ -62318,22 +62358,42 @@ function extractFunctionsFromStatement(stmt) {
|
|
|
62318
62358
|
function getFunctionPermission(name) {
|
|
62319
62359
|
return functionPermissions.get(name) ?? "read";
|
|
62320
62360
|
}
|
|
62361
|
+
var TRANSACTION_UNSAFE_KINDS = /* @__PURE__ */ new Set([
|
|
62362
|
+
"create_db",
|
|
62363
|
+
"drop_db",
|
|
62364
|
+
"create_tablespace",
|
|
62365
|
+
"drop_tablespace",
|
|
62366
|
+
"alter_system",
|
|
62367
|
+
"vacuum",
|
|
62368
|
+
"cluster",
|
|
62369
|
+
"reindex"
|
|
62370
|
+
]);
|
|
62321
62371
|
async function detectRequiredPermissions(sql) {
|
|
62322
62372
|
try {
|
|
62323
62373
|
const parsed = await parseSql(sql);
|
|
62324
62374
|
if (parsed.statements.length === 0) {
|
|
62325
|
-
return /* @__PURE__ */ new Set(["read"]);
|
|
62375
|
+
return { permissions: /* @__PURE__ */ new Set(["read"]), statementCount: 0, transactionSafe: true };
|
|
62326
62376
|
}
|
|
62327
62377
|
const permissions = /* @__PURE__ */ new Set();
|
|
62378
|
+
let transactionSafe = true;
|
|
62328
62379
|
for (const stmt of parsed.statements) {
|
|
62329
62380
|
permissions.add(getRequiredPermission(stmt.kind));
|
|
62381
|
+
if (TRANSACTION_UNSAFE_KINDS.has(stmt.kind) || stmt.kind === "transaction") {
|
|
62382
|
+
transactionSafe = false;
|
|
62383
|
+
}
|
|
62384
|
+
if (stmt.kind === "create_index" && stmt.concurrent) {
|
|
62385
|
+
transactionSafe = false;
|
|
62386
|
+
}
|
|
62387
|
+
if (stmt.kind === "drop" && stmt.objectType === "index" && /\bCONCURRENTLY\b/i.test(stmt.source)) {
|
|
62388
|
+
transactionSafe = false;
|
|
62389
|
+
}
|
|
62330
62390
|
for (const name of extractFunctionsFromStatement(stmt)) {
|
|
62331
62391
|
permissions.add(getFunctionPermission(name));
|
|
62332
62392
|
}
|
|
62333
62393
|
}
|
|
62334
|
-
return permissions;
|
|
62394
|
+
return { permissions, statementCount: parsed.statements.length, transactionSafe };
|
|
62335
62395
|
} catch {
|
|
62336
|
-
return /* @__PURE__ */ new Set(["admin"]);
|
|
62396
|
+
return { permissions: /* @__PURE__ */ new Set(["admin"]), statementCount: 1, transactionSafe: false };
|
|
62337
62397
|
}
|
|
62338
62398
|
}
|
|
62339
62399
|
|
|
@@ -62445,8 +62505,8 @@ var queryServiceHandlers = {
|
|
|
62445
62505
|
if (!user) {
|
|
62446
62506
|
throw new ConnectError("Authentication required", Code.Unauthenticated);
|
|
62447
62507
|
}
|
|
62448
|
-
const
|
|
62449
|
-
requirePermissions(user, req.connectionId,
|
|
62508
|
+
const analysis = await detectRequiredPermissions(req.sql);
|
|
62509
|
+
requirePermissions(user, req.connectionId, analysis.permissions, `execute query`);
|
|
62450
62510
|
const details = getConnectionDetails(req.connectionId);
|
|
62451
62511
|
const queryId = req.queryId;
|
|
62452
62512
|
const client = createClient(details, user.email);
|
|
@@ -62473,7 +62533,10 @@ var queryServiceHandlers = {
|
|
|
62473
62533
|
error: "",
|
|
62474
62534
|
backendPid
|
|
62475
62535
|
};
|
|
62476
|
-
const
|
|
62536
|
+
const sql = analysis.statementCount > 1 && analysis.transactionSafe ? `BEGIN;
|
|
62537
|
+
${req.sql}
|
|
62538
|
+
COMMIT;` : req.sql;
|
|
62539
|
+
const result = await client.unsafe(sql);
|
|
62477
62540
|
const executionTimeMs = Date.now() - start2;
|
|
62478
62541
|
let columnMeta = [];
|
|
62479
62542
|
if (result.columns) {
|
|
@@ -62796,9 +62859,16 @@ var queryServiceHandlers = {
|
|
|
62796
62859
|
const user = await getUserFromContext(context.values);
|
|
62797
62860
|
requireAnyPermission(user, req.connectionId);
|
|
62798
62861
|
const details = getConnectionDetails(req.connectionId);
|
|
62799
|
-
const
|
|
62862
|
+
const result = await withConnection(details, async (sql) => {
|
|
62800
62863
|
const rows = await sql`
|
|
62864
|
+
WITH target AS (
|
|
62865
|
+
SELECT c.oid FROM pg_class c
|
|
62866
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
62867
|
+
WHERE n.nspname = ${req.schema} AND c.relname = ${req.table}
|
|
62868
|
+
)
|
|
62869
|
+
-- Forward: constraints on this table
|
|
62801
62870
|
SELECT
|
|
62871
|
+
'constraint' as source,
|
|
62802
62872
|
con.conname as name,
|
|
62803
62873
|
CASE con.contype
|
|
62804
62874
|
WHEN 'p' THEN 'PRIMARY KEY'
|
|
@@ -62817,26 +62887,42 @@ var queryServiceHandlers = {
|
|
|
62817
62887
|
FROM pg_attribute af WHERE af.attrelid = con.confrelid AND af.attnum = ANY(con.confkey))
|
|
62818
62888
|
END as ref_columns
|
|
62819
62889
|
FROM pg_constraint con
|
|
62820
|
-
JOIN
|
|
62821
|
-
JOIN
|
|
62822
|
-
LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(con.conkey)
|
|
62823
|
-
WHERE n.nspname = ${req.schema}
|
|
62824
|
-
AND c.relname = ${req.table}
|
|
62890
|
+
JOIN target t ON con.conrelid = t.oid
|
|
62891
|
+
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(con.conkey)
|
|
62825
62892
|
GROUP BY con.oid, con.conname, con.contype, con.confrelid, con.confkey
|
|
62826
|
-
|
|
62827
|
-
|
|
62828
|
-
|
|
62893
|
+
UNION ALL
|
|
62894
|
+
-- Reverse: FK constraints from other tables referencing this table
|
|
62895
|
+
SELECT
|
|
62896
|
+
'referenced_by' as source,
|
|
62897
|
+
con.conname as name,
|
|
62898
|
+
'FOREIGN KEY' as type,
|
|
62899
|
+
(SELECT array_agg(a.attname ORDER BY array_position(con.conkey, a.attnum))
|
|
62900
|
+
FROM pg_attribute a WHERE a.attrelid = con.conrelid AND a.attnum = ANY(con.conkey)) as columns,
|
|
62901
|
+
pg_get_constraintdef(con.oid) as definition,
|
|
62902
|
+
(SELECT ns.nspname || '.' || cs.relname FROM pg_class cs JOIN pg_namespace ns ON ns.oid = cs.relnamespace WHERE cs.oid = con.conrelid) as ref_table,
|
|
62903
|
+
(SELECT array_agg(af.attname ORDER BY array_position(con.confkey, af.attnum))
|
|
62904
|
+
FROM pg_attribute af WHERE af.attrelid = con.confrelid AND af.attnum = ANY(con.confkey)) as ref_columns
|
|
62905
|
+
FROM pg_constraint con
|
|
62906
|
+
JOIN target t ON con.confrelid = t.oid
|
|
62907
|
+
WHERE con.contype = 'f'
|
|
62829
62908
|
`;
|
|
62830
|
-
|
|
62909
|
+
const mapRow = (r2) => ({
|
|
62831
62910
|
name: r2.name,
|
|
62832
62911
|
type: r2.type,
|
|
62833
62912
|
columns: r2.columns || [],
|
|
62834
62913
|
definition: r2.definition,
|
|
62835
62914
|
refTable: r2.ref_table || "",
|
|
62836
62915
|
refColumns: r2.ref_columns || []
|
|
62837
|
-
})
|
|
62916
|
+
});
|
|
62917
|
+
const constraints = rows.filter((r2) => r2.source === "constraint").sort((a, b) => {
|
|
62918
|
+
const order = { "PRIMARY KEY": 1, "FOREIGN KEY": 2, "UNIQUE": 3, "CHECK": 4, "EXCLUDE": 5 };
|
|
62919
|
+
const diff = (order[a.type] ?? 6) - (order[b.type] ?? 6);
|
|
62920
|
+
return diff !== 0 ? diff : a.name.localeCompare(b.name);
|
|
62921
|
+
}).map(mapRow);
|
|
62922
|
+
const referencedBy = rows.filter((r2) => r2.source === "referenced_by").map(mapRow);
|
|
62923
|
+
return { constraints, referencedBy };
|
|
62838
62924
|
}, user?.email);
|
|
62839
|
-
return
|
|
62925
|
+
return result;
|
|
62840
62926
|
},
|
|
62841
62927
|
async getTriggers(req, context) {
|
|
62842
62928
|
if (!req.connectionId) {
|
|
@@ -92198,9 +92284,9 @@ var ASSESS_RISK = {
|
|
|
92198
92284
|
|
|
92199
92285
|
## Focus Areas
|
|
92200
92286
|
|
|
92201
|
-
- **
|
|
92202
|
-
- **
|
|
92203
|
-
- **Dependent Objects**: Impact on views, materialized views, functions, triggers
|
|
92287
|
+
- **Foreign Key Cascading**: Trace every foreign key chain in the schema. A DELETE or UPDATE on a parent table may CASCADE to child tables, which may in turn cascade further. Flag the full cascade path and estimate affected scope. Also flag operations that would violate FK constraints (inserting with non-existent references, dropping referenced columns/tables).
|
|
92288
|
+
- **Data Loss**: Permanent deletion of data, large-scale operations (100+ rows), TRUNCATE
|
|
92289
|
+
- **Dependent Objects**: Impact on views, materialized views, functions, triggers that reference the affected tables
|
|
92204
92290
|
- **Critical Columns**: Changes to primary keys, foreign keys, unique constraints
|
|
92205
92291
|
- **Transaction Scope**: Multi-table operations, data consistency concerns
|
|
92206
92292
|
|
|
@@ -92223,7 +92309,19 @@ The operations appear safe with no major integrity concerns detected.
|
|
|
92223
92309
|
- Focus on data integrity risks, not query performance
|
|
92224
92310
|
- Consider cascading effects and dependent objects
|
|
92225
92311
|
- Identify potential data loss or corruption scenarios
|
|
92226
|
-
- Keep descriptions concise but actionable
|
|
92312
|
+
- Keep descriptions concise but actionable
|
|
92313
|
+
|
|
92314
|
+
## Dependency Graph
|
|
92315
|
+
|
|
92316
|
+
If the operations involve dependencies between database objects, include a Mermaid flowchart showing the affected tables and how changes propagate. Use this format:
|
|
92317
|
+
|
|
92318
|
+
\`\`\`mermaid
|
|
92319
|
+
graph TD
|
|
92320
|
+
orders -->|CASCADE DELETE| order_items
|
|
92321
|
+
products -->|RESTRICT| order_items
|
|
92322
|
+
\`\`\`
|
|
92323
|
+
|
|
92324
|
+
Only include the graph if there are meaningful dependencies. Omit it for simple, independent operations.`,
|
|
92227
92325
|
user: ({ sqlStatements }) => `Assess the risk of executing these SQL statements:
|
|
92228
92326
|
|
|
92229
92327
|
${sqlStatements}`
|
|
@@ -92266,6 +92364,12 @@ async function getOrRefreshSchema(connectionId, schemas) {
|
|
|
92266
92364
|
return cached.formatted;
|
|
92267
92365
|
}
|
|
92268
92366
|
function parseRiskAssessment(response) {
|
|
92367
|
+
let dependencyGraph = "";
|
|
92368
|
+
const mermaidMatch = response.match(/```mermaid\n([\s\S]*?)```/);
|
|
92369
|
+
if (mermaidMatch) {
|
|
92370
|
+
dependencyGraph = mermaidMatch[1].trim();
|
|
92371
|
+
response = response.replace(/```mermaid\n[\s\S]*?```/, "").trim();
|
|
92372
|
+
}
|
|
92269
92373
|
const lines = response.trim().split("\n");
|
|
92270
92374
|
const firstLine = lines[0].trim().toUpperCase();
|
|
92271
92375
|
const overallRisk = firstLine.match(/\b(HIGH|MODERATE|LOW)\b/)?.[1] || "MODERATE";
|
|
@@ -92297,7 +92401,7 @@ function parseRiskAssessment(response) {
|
|
|
92297
92401
|
description: response.substring(firstLine.length).trim() || "Risk assessment completed."
|
|
92298
92402
|
});
|
|
92299
92403
|
}
|
|
92300
|
-
return { overallRisk: overallRisk.toLowerCase(), findings };
|
|
92404
|
+
return { overallRisk: overallRisk.toLowerCase(), findings, dependencyGraph };
|
|
92301
92405
|
}
|
|
92302
92406
|
var aiServiceHandlers = {
|
|
92303
92407
|
async listAIProviders() {
|
|
@@ -92489,11 +92593,12 @@ var aiServiceHandlers = {
|
|
|
92489
92593
|
return {
|
|
92490
92594
|
overallRisk: parsed.overallRisk,
|
|
92491
92595
|
findings: parsed.findings,
|
|
92492
|
-
error: ""
|
|
92596
|
+
error: "",
|
|
92597
|
+
dependencyGraph: parsed.dependencyGraph
|
|
92493
92598
|
};
|
|
92494
92599
|
} catch (err) {
|
|
92495
92600
|
const message2 = err instanceof Error ? err.message : "Failed to assess change risk";
|
|
92496
|
-
return { overallRisk: "", findings: [], error: message2 };
|
|
92601
|
+
return { overallRisk: "", findings: [], error: message2, dependencyGraph: "" };
|
|
92497
92602
|
}
|
|
92498
92603
|
}
|
|
92499
92604
|
};
|
|
@@ -92527,6 +92632,183 @@ var connectRouter = expressConnectMiddleware({
|
|
|
92527
92632
|
}
|
|
92528
92633
|
});
|
|
92529
92634
|
|
|
92635
|
+
// server/index.ts
|
|
92636
|
+
import { existsSync as existsSync2 } from "fs";
|
|
92637
|
+
|
|
92638
|
+
// server/lib/demo.ts
|
|
92639
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
92640
|
+
import { PGLiteSocketServer } from "@electric-sql/pglite-socket";
|
|
92641
|
+
var db;
|
|
92642
|
+
var socketServer;
|
|
92643
|
+
var SEED_SQL = `
|
|
92644
|
+
CREATE TABLE departments (
|
|
92645
|
+
id SERIAL PRIMARY KEY,
|
|
92646
|
+
name VARCHAR(100) NOT NULL,
|
|
92647
|
+
location VARCHAR(100),
|
|
92648
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
92649
|
+
);
|
|
92650
|
+
|
|
92651
|
+
CREATE TABLE employees (
|
|
92652
|
+
id SERIAL PRIMARY KEY,
|
|
92653
|
+
first_name VARCHAR(50) NOT NULL,
|
|
92654
|
+
last_name VARCHAR(50) NOT NULL,
|
|
92655
|
+
email VARCHAR(100) UNIQUE NOT NULL,
|
|
92656
|
+
department_id INTEGER REFERENCES departments(id),
|
|
92657
|
+
title VARCHAR(100),
|
|
92658
|
+
salary NUMERIC(10,2),
|
|
92659
|
+
hire_date DATE DEFAULT CURRENT_DATE,
|
|
92660
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
92661
|
+
);
|
|
92662
|
+
|
|
92663
|
+
CREATE TABLE projects (
|
|
92664
|
+
id SERIAL PRIMARY KEY,
|
|
92665
|
+
name VARCHAR(100) NOT NULL,
|
|
92666
|
+
description TEXT,
|
|
92667
|
+
department_id INTEGER REFERENCES departments(id),
|
|
92668
|
+
lead_id INTEGER REFERENCES employees(id),
|
|
92669
|
+
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'completed', 'on_hold')),
|
|
92670
|
+
start_date DATE DEFAULT CURRENT_DATE,
|
|
92671
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
92672
|
+
);
|
|
92673
|
+
|
|
92674
|
+
CREATE VIEW employee_directory AS
|
|
92675
|
+
SELECT
|
|
92676
|
+
e.id,
|
|
92677
|
+
e.first_name || ' ' || e.last_name AS full_name,
|
|
92678
|
+
e.email,
|
|
92679
|
+
e.title,
|
|
92680
|
+
d.name AS department,
|
|
92681
|
+
d.location
|
|
92682
|
+
FROM employees e
|
|
92683
|
+
LEFT JOIN departments d ON e.department_id = d.id
|
|
92684
|
+
ORDER BY e.last_name, e.first_name;
|
|
92685
|
+
|
|
92686
|
+
INSERT INTO departments (name, location) VALUES
|
|
92687
|
+
('Engineering', 'San Francisco'),
|
|
92688
|
+
('Marketing', 'New York'),
|
|
92689
|
+
('Sales', 'Chicago'),
|
|
92690
|
+
('Human Resources', 'San Francisco');
|
|
92691
|
+
|
|
92692
|
+
INSERT INTO employees (first_name, last_name, email, department_id, title, salary, hire_date) VALUES
|
|
92693
|
+
('Alice', 'Chen', 'alice.chen@example.com', 1, 'Staff Engineer', 185000, '2021-03-15'),
|
|
92694
|
+
('Bob', 'Smith', 'bob.smith@example.com', 1, 'Senior Engineer', 165000, '2022-01-10'),
|
|
92695
|
+
('Carol', 'Johnson', 'carol.johnson@example.com', 2, 'Marketing Director', 155000, '2020-06-01'),
|
|
92696
|
+
('David', 'Williams', 'david.williams@example.com', 3, 'Sales Manager', 130000, '2023-02-20'),
|
|
92697
|
+
('Eva', 'Martinez', 'eva.martinez@example.com', 1, 'Engineer', 140000, '2023-07-01'),
|
|
92698
|
+
('Frank', 'Brown', 'frank.brown@example.com', 2, 'Content Strategist', 110000, '2023-09-15'),
|
|
92699
|
+
('Grace', 'Lee', 'grace.lee@example.com', 4, 'HR Manager', 125000, '2021-11-01'),
|
|
92700
|
+
('Henry', 'Taylor', 'henry.taylor@example.com', 3, 'Account Executive', 120000, '2024-01-08'),
|
|
92701
|
+
('Iris', 'Garcia', 'iris.garcia@example.com', 1, 'Engineering Manager', 175000, '2020-09-12'),
|
|
92702
|
+
('Jack', 'Wilson', 'jack.wilson@example.com', 2, 'Designer', 115000, '2024-03-01');
|
|
92703
|
+
|
|
92704
|
+
INSERT INTO projects (name, description, department_id, lead_id, status, start_date) VALUES
|
|
92705
|
+
('Platform Redesign', 'Modernize the core platform architecture', 1, 1, 'active', '2024-01-15'),
|
|
92706
|
+
('Brand Refresh', 'Update brand guidelines and marketing materials', 2, 3, 'active', '2024-02-01'),
|
|
92707
|
+
('Q1 Sales Campaign', 'Enterprise outreach for Q1', 3, 4, 'completed', '2024-01-01'),
|
|
92708
|
+
('Employee Portal', 'Internal HR self-service portal', 4, 7, 'active', '2024-03-01'),
|
|
92709
|
+
('API v2', 'Next generation public API', 1, 9, 'on_hold', '2024-04-01');
|
|
92710
|
+
|
|
92711
|
+
-- Materialized view: department summary stats
|
|
92712
|
+
CREATE MATERIALIZED VIEW department_stats AS
|
|
92713
|
+
SELECT
|
|
92714
|
+
d.id AS department_id,
|
|
92715
|
+
d.name AS department,
|
|
92716
|
+
COUNT(e.id) AS employee_count,
|
|
92717
|
+
COALESCE(ROUND(AVG(e.salary), 2), 0) AS avg_salary,
|
|
92718
|
+
COALESCE(MIN(e.hire_date), CURRENT_DATE) AS earliest_hire,
|
|
92719
|
+
COUNT(p.id) AS project_count
|
|
92720
|
+
FROM departments d
|
|
92721
|
+
LEFT JOIN employees e ON e.department_id = d.id
|
|
92722
|
+
LEFT JOIN projects p ON p.department_id = d.id
|
|
92723
|
+
GROUP BY d.id, d.name;
|
|
92724
|
+
|
|
92725
|
+
-- Function: look up employees by department name
|
|
92726
|
+
CREATE FUNCTION get_employees_by_department(dept_name TEXT)
|
|
92727
|
+
RETURNS TABLE(id INT, full_name TEXT, title VARCHAR, salary NUMERIC) AS $$
|
|
92728
|
+
BEGIN
|
|
92729
|
+
RETURN QUERY
|
|
92730
|
+
SELECT e.id, e.first_name || ' ' || e.last_name, e.title, e.salary
|
|
92731
|
+
FROM employees e
|
|
92732
|
+
JOIN departments d ON e.department_id = d.id
|
|
92733
|
+
WHERE d.name = dept_name
|
|
92734
|
+
ORDER BY e.last_name;
|
|
92735
|
+
END;
|
|
92736
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
92737
|
+
|
|
92738
|
+
-- Procedure: give a percentage raise to all employees in a department
|
|
92739
|
+
CREATE PROCEDURE give_department_raise(dept_name TEXT, pct NUMERIC)
|
|
92740
|
+
LANGUAGE plpgsql AS $$
|
|
92741
|
+
BEGIN
|
|
92742
|
+
UPDATE employees SET salary = ROUND(salary * (1 + pct / 100), 2)
|
|
92743
|
+
WHERE department_id = (SELECT id FROM departments WHERE name = dept_name);
|
|
92744
|
+
END;
|
|
92745
|
+
$$;
|
|
92746
|
+
|
|
92747
|
+
-- analytics schema
|
|
92748
|
+
CREATE SCHEMA analytics;
|
|
92749
|
+
|
|
92750
|
+
CREATE TABLE analytics.page_views (
|
|
92751
|
+
id SERIAL PRIMARY KEY,
|
|
92752
|
+
path VARCHAR(200) NOT NULL,
|
|
92753
|
+
visitor_id UUID NOT NULL,
|
|
92754
|
+
referrer VARCHAR(500),
|
|
92755
|
+
duration_ms INTEGER,
|
|
92756
|
+
viewed_at TIMESTAMP DEFAULT NOW()
|
|
92757
|
+
);
|
|
92758
|
+
|
|
92759
|
+
CREATE TABLE analytics.events (
|
|
92760
|
+
id SERIAL PRIMARY KEY,
|
|
92761
|
+
name VARCHAR(100) NOT NULL,
|
|
92762
|
+
visitor_id UUID NOT NULL,
|
|
92763
|
+
properties JSONB DEFAULT '{}',
|
|
92764
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
92765
|
+
);
|
|
92766
|
+
|
|
92767
|
+
INSERT INTO analytics.page_views (path, visitor_id, referrer, duration_ms) VALUES
|
|
92768
|
+
('/', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'https://google.com', 4200),
|
|
92769
|
+
('/pricing', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '/', 8500),
|
|
92770
|
+
('/', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', 'https://github.com', 3100),
|
|
92771
|
+
('/docs', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', '/', 12400),
|
|
92772
|
+
('/pricing', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', 'https://twitter.com', 6700),
|
|
92773
|
+
('/', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', NULL, 2300);
|
|
92774
|
+
|
|
92775
|
+
INSERT INTO analytics.events (name, visitor_id, properties) VALUES
|
|
92776
|
+
('signup_click', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '{"plan": "pro"}'),
|
|
92777
|
+
('page_scroll', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', '{"depth": 75}'),
|
|
92778
|
+
('signup_click', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', '{"plan": "free"}'),
|
|
92779
|
+
('feature_click', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '{"feature": "sql-editor"}');
|
|
92780
|
+
|
|
92781
|
+
CREATE VIEW analytics.daily_summary AS
|
|
92782
|
+
SELECT
|
|
92783
|
+
viewed_at::date AS day,
|
|
92784
|
+
COUNT(*) AS views,
|
|
92785
|
+
COUNT(DISTINCT visitor_id) AS unique_visitors,
|
|
92786
|
+
ROUND(AVG(duration_ms)) AS avg_duration_ms
|
|
92787
|
+
FROM analytics.page_views
|
|
92788
|
+
GROUP BY viewed_at::date;
|
|
92789
|
+
`;
|
|
92790
|
+
async function startDemoDatabase() {
|
|
92791
|
+
db = await PGlite.create({ debug: 0 });
|
|
92792
|
+
await db.exec(SEED_SQL);
|
|
92793
|
+
return new Promise((resolve, reject) => {
|
|
92794
|
+
socketServer = new PGLiteSocketServer({ db, port: 0 });
|
|
92795
|
+
socketServer.addEventListener("listening", (event) => {
|
|
92796
|
+
const detail = event.detail;
|
|
92797
|
+
resolve(detail.port);
|
|
92798
|
+
});
|
|
92799
|
+
socketServer.addEventListener("error", (event) => {
|
|
92800
|
+
reject(new Error(`Failed to start demo database: ${event}`));
|
|
92801
|
+
});
|
|
92802
|
+
socketServer.start();
|
|
92803
|
+
});
|
|
92804
|
+
}
|
|
92805
|
+
async function stopDemoDatabase() {
|
|
92806
|
+
await socketServer?.stop();
|
|
92807
|
+
await db?.close();
|
|
92808
|
+
db = void 0;
|
|
92809
|
+
socketServer = void 0;
|
|
92810
|
+
}
|
|
92811
|
+
|
|
92530
92812
|
// server/lib/test-connections.ts
|
|
92531
92813
|
async function testAllConnections() {
|
|
92532
92814
|
const connections = getConnections();
|
|
@@ -92610,7 +92892,8 @@ app.get("/api/setting", (_req, res) => {
|
|
|
92610
92892
|
licenseEmail: getLicenseEmail(),
|
|
92611
92893
|
maxUsers: getLicenseMaxUsers(),
|
|
92612
92894
|
userCount: getUsers().length,
|
|
92613
|
-
stripeCustomerId: getStripeCustomerId()
|
|
92895
|
+
stripeCustomerId: getStripeCustomerId(),
|
|
92896
|
+
demo: isDemoMode()
|
|
92614
92897
|
});
|
|
92615
92898
|
});
|
|
92616
92899
|
var clientDir = path4.join(__dirname, "client");
|
|
@@ -92631,12 +92914,19 @@ async function start() {
|
|
|
92631
92914
|
const args = parseArgs();
|
|
92632
92915
|
const port = args.port || process.env.PORT || 9876;
|
|
92633
92916
|
const configPath = args.config || "pgconsole.toml";
|
|
92634
|
-
|
|
92635
|
-
|
|
92636
|
-
|
|
92637
|
-
|
|
92638
|
-
console.
|
|
92639
|
-
|
|
92917
|
+
if (!args.config && !existsSync2(configPath)) {
|
|
92918
|
+
console.log("No pgconsole.toml found \u2014 starting in demo mode...");
|
|
92919
|
+
const demoPort = await startDemoDatabase();
|
|
92920
|
+
loadDemoConfig(demoPort);
|
|
92921
|
+
console.log(`\u2713 Demo database started on port ${demoPort}`);
|
|
92922
|
+
} else {
|
|
92923
|
+
try {
|
|
92924
|
+
await loadConfig(configPath);
|
|
92925
|
+
console.log(`\u2713 Loaded config from: ${path4.resolve(configPath)}`);
|
|
92926
|
+
} catch (error) {
|
|
92927
|
+
console.error("Failed to load config:", error instanceof Error ? error.message : error);
|
|
92928
|
+
process.exit(1);
|
|
92929
|
+
}
|
|
92640
92930
|
}
|
|
92641
92931
|
const licenseExpiry = getLicenseExpiry();
|
|
92642
92932
|
if (licenseExpiry) {
|
|
@@ -92663,7 +92953,7 @@ async function start() {
|
|
|
92663
92953
|
\\ \\_\\ /\\____/
|
|
92664
92954
|
\\/_/ \\_/__/
|
|
92665
92955
|
|
|
92666
|
-
Version ${"0.
|
|
92956
|
+
Version ${"0.2.0"}
|
|
92667
92957
|
`);
|
|
92668
92958
|
console.log(`Server running on http://localhost:${port}`);
|
|
92669
92959
|
const browserUrl = false ? `http://localhost:5173` : getExternalUrl() || `http://localhost:${port}`;
|
|
@@ -92677,8 +92967,12 @@ async function start() {
|
|
|
92677
92967
|
}
|
|
92678
92968
|
process.exit(1);
|
|
92679
92969
|
});
|
|
92680
|
-
const shutdown = () => {
|
|
92970
|
+
const shutdown = async () => {
|
|
92681
92971
|
console.log("\nShutting down...");
|
|
92972
|
+
try {
|
|
92973
|
+
if (isDemoMode()) await stopDemoDatabase();
|
|
92974
|
+
} catch {
|
|
92975
|
+
}
|
|
92682
92976
|
server.close(() => {
|
|
92683
92977
|
process.exit(0);
|
|
92684
92978
|
});
|