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