@tekki-forge/runtime-api 0.2.0 → 0.3.2
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/dist/admin.d.ts +146 -0
- package/dist/admin.js +256 -0
- package/dist/admin.js.map +1 -0
- package/dist/bootstrap.d.ts +39 -0
- package/dist/bootstrap.js +99 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/controllers.d.ts +273 -4
- package/dist/controllers.js +157 -7
- package/dist/controllers.js.map +1 -1
- package/dist/dashboard.d.ts +102 -5
- package/dist/dashboard.js +491 -73
- package/dist/dashboard.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.js.map +1 -1
- package/dist/seed.js +10 -0
- package/dist/seed.js.map +1 -1
- package/dist/service.d.ts +3 -0
- package/dist/service.js +15 -2
- package/dist/service.js.map +1 -1
- package/dist/settings.d.ts +88 -0
- package/dist/settings.js +252 -0
- package/dist/settings.js.map +1 -0
- package/dist/site-controller.d.ts +115 -0
- package/dist/site-controller.js +373 -0
- package/dist/site-controller.js.map +1 -0
- package/dist/site.d.ts +410 -0
- package/dist/site.js +1413 -0
- package/dist/site.js.map +1 -0
- package/dist/support.d.ts +175 -0
- package/dist/support.js +251 -0
- package/dist/support.js.map +1 -0
- package/package.json +5 -5
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { ForgeRequestContext } from "./extension.js";
|
|
2
|
+
import type { ForgeService } from "./service.js";
|
|
3
|
+
type Row = Record<string, any>;
|
|
4
|
+
/**
|
|
5
|
+
* Who sees what in the administration area:
|
|
6
|
+
* - global admins (the `admin` role on their account) see the whole system;
|
|
7
|
+
* - with organizations, organization admins (member role admin/owner) see their own organization only;
|
|
8
|
+
* - everyone else gets 403. API keys never reach this API.
|
|
9
|
+
*/
|
|
10
|
+
export interface AdminScope {
|
|
11
|
+
global: boolean;
|
|
12
|
+
/** Organization the data is limited to (organization admins), or null for everything. */
|
|
13
|
+
organizationId: string | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function adminScope(service: ForgeService, ctx: ForgeRequestContext, opts?: {
|
|
16
|
+
globalOnly?: boolean;
|
|
17
|
+
}): AdminScope;
|
|
18
|
+
export interface SeriesPoint {
|
|
19
|
+
/** Bucket start: "2026-10-02" (day) or "2026-10" (month). */
|
|
20
|
+
key: string;
|
|
21
|
+
value: number;
|
|
22
|
+
}
|
|
23
|
+
/** Every bucket key between `since` and today, so charts show the empty days too. */
|
|
24
|
+
export declare function bucketKeys(since: Date, by: "day" | "month", now?: Date): string[];
|
|
25
|
+
/** System overview, analytics and organizations for Administration (see adminScope for who sees what). */
|
|
26
|
+
export declare class AdminService {
|
|
27
|
+
private readonly service;
|
|
28
|
+
constructor(service: ForgeService);
|
|
29
|
+
private get prisma();
|
|
30
|
+
private get app();
|
|
31
|
+
/** User entities (not the tables plugins add for their own bookkeeping). */
|
|
32
|
+
private entities;
|
|
33
|
+
private userWhere;
|
|
34
|
+
/** Accounts created (global) or members who joined the organization, since `since`. */
|
|
35
|
+
private joinedSince;
|
|
36
|
+
overview(ctx: ForgeRequestContext): Promise<{
|
|
37
|
+
scope: "global" | "organization";
|
|
38
|
+
organizationId: string | null;
|
|
39
|
+
users: {
|
|
40
|
+
total: any;
|
|
41
|
+
newThisWeek: number;
|
|
42
|
+
newPreviousWeek: number;
|
|
43
|
+
weekly: SeriesPoint[];
|
|
44
|
+
};
|
|
45
|
+
organizations: {
|
|
46
|
+
total: any;
|
|
47
|
+
newThisWeek: any;
|
|
48
|
+
} | null;
|
|
49
|
+
sessions: {
|
|
50
|
+
active: any;
|
|
51
|
+
};
|
|
52
|
+
records: {
|
|
53
|
+
entity: string;
|
|
54
|
+
label: string;
|
|
55
|
+
path: string;
|
|
56
|
+
total: any;
|
|
57
|
+
last30: any;
|
|
58
|
+
}[];
|
|
59
|
+
recentSignups: any;
|
|
60
|
+
recentAudit: any;
|
|
61
|
+
jobs: {
|
|
62
|
+
failed: number;
|
|
63
|
+
active: number;
|
|
64
|
+
waiting: number;
|
|
65
|
+
delayed: number;
|
|
66
|
+
} | null;
|
|
67
|
+
webhooks: {
|
|
68
|
+
failed: number;
|
|
69
|
+
succeeded: number;
|
|
70
|
+
pending: number;
|
|
71
|
+
} | null;
|
|
72
|
+
billing: unknown;
|
|
73
|
+
support: {
|
|
74
|
+
open: number;
|
|
75
|
+
pending: number;
|
|
76
|
+
unassigned: number;
|
|
77
|
+
} | null;
|
|
78
|
+
}>;
|
|
79
|
+
/** New accounts (or members) per week, oldest first, for the trend sparkline. */
|
|
80
|
+
private weeklyJoins;
|
|
81
|
+
private jobs;
|
|
82
|
+
private webhooks;
|
|
83
|
+
private billing;
|
|
84
|
+
private support;
|
|
85
|
+
/**
|
|
86
|
+
* Time series for Administration → Analytics: sign-ups (or members joining), active users (people with
|
|
87
|
+
* a session used that day) and records created per entity. `?days=7|30|90|365` (365 is bucketed by month).
|
|
88
|
+
*/
|
|
89
|
+
analytics(ctx: ForgeRequestContext, query?: Row): Promise<{
|
|
90
|
+
scope: "global" | "organization";
|
|
91
|
+
days: number;
|
|
92
|
+
by: "month" | "day";
|
|
93
|
+
since: string;
|
|
94
|
+
signups: {
|
|
95
|
+
total: number;
|
|
96
|
+
series: SeriesPoint[];
|
|
97
|
+
};
|
|
98
|
+
activeUsers: {
|
|
99
|
+
total: number;
|
|
100
|
+
series: SeriesPoint[];
|
|
101
|
+
};
|
|
102
|
+
records: {
|
|
103
|
+
entity: string;
|
|
104
|
+
label: string;
|
|
105
|
+
path: string;
|
|
106
|
+
total: number;
|
|
107
|
+
series: SeriesPoint[];
|
|
108
|
+
}[];
|
|
109
|
+
topEntities: {
|
|
110
|
+
entity: string;
|
|
111
|
+
label: string;
|
|
112
|
+
path: string;
|
|
113
|
+
total: number;
|
|
114
|
+
}[];
|
|
115
|
+
}>;
|
|
116
|
+
/** Every organization with its member count and plan (payments plugin). Global admins only. */
|
|
117
|
+
organizations(ctx: ForgeRequestContext, query?: Row): Promise<{
|
|
118
|
+
data: any;
|
|
119
|
+
meta: {
|
|
120
|
+
page: number;
|
|
121
|
+
pageSize: number;
|
|
122
|
+
total: any;
|
|
123
|
+
pages: number;
|
|
124
|
+
};
|
|
125
|
+
billing: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
/** One organization: members and their roles, pending invitations, plan and record counts. Global admins only. */
|
|
128
|
+
organization(id: string, ctx: ForgeRequestContext): Promise<{
|
|
129
|
+
id: any;
|
|
130
|
+
name: any;
|
|
131
|
+
slug: any;
|
|
132
|
+
logo: any;
|
|
133
|
+
createdAt: any;
|
|
134
|
+
plan: {} | null;
|
|
135
|
+
members: any;
|
|
136
|
+
pendingInvitations: any;
|
|
137
|
+
records: {
|
|
138
|
+
entity: string;
|
|
139
|
+
label: string;
|
|
140
|
+
path: string;
|
|
141
|
+
total: any;
|
|
142
|
+
}[];
|
|
143
|
+
}>;
|
|
144
|
+
private plans;
|
|
145
|
+
}
|
|
146
|
+
export {};
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { ForbiddenException, NotFoundException, UnauthorizedException } from "@nestjs/common";
|
|
2
|
+
import { isGlobalAdminRole } from "./settings.js";
|
|
3
|
+
export function adminScope(service, ctx, opts = {}) {
|
|
4
|
+
if (!service.app.admin.enabled)
|
|
5
|
+
throw new NotFoundException("The administration area is disabled (admin.enabled: false)");
|
|
6
|
+
if (!ctx.principal)
|
|
7
|
+
throw new UnauthorizedException("Sign in to continue");
|
|
8
|
+
if (ctx.apiKey)
|
|
9
|
+
throw new ForbiddenException("API keys cannot use the administration API");
|
|
10
|
+
if (isGlobalAdminRole(ctx.user?.role))
|
|
11
|
+
return { global: true, organizationId: null };
|
|
12
|
+
if (!opts.globalOnly && service.app.tenancy.enabled && ctx.principal.organizationId && ctx.principal.roles.includes("admin")) {
|
|
13
|
+
return { global: false, organizationId: ctx.principal.organizationId };
|
|
14
|
+
}
|
|
15
|
+
throw new ForbiddenException(opts.globalOnly ? "Only global administrators can see this" : "Only administrators can use the administration area");
|
|
16
|
+
}
|
|
17
|
+
const DAY = 86_400_000;
|
|
18
|
+
const q = (name) => `"${name.replace(/"/g, '""')}"`;
|
|
19
|
+
function startOfUtcDay(d) {
|
|
20
|
+
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
21
|
+
}
|
|
22
|
+
/** Every bucket key between `since` and today, so charts show the empty days too. */
|
|
23
|
+
export function bucketKeys(since, by, now = new Date()) {
|
|
24
|
+
const keys = [];
|
|
25
|
+
if (by === "month") {
|
|
26
|
+
const d = new Date(Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), 1));
|
|
27
|
+
while (d <= now) {
|
|
28
|
+
keys.push(d.toISOString().slice(0, 7));
|
|
29
|
+
d.setUTCMonth(d.getUTCMonth() + 1);
|
|
30
|
+
}
|
|
31
|
+
return keys;
|
|
32
|
+
}
|
|
33
|
+
for (let d = startOfUtcDay(since); d <= now; d = new Date(d.getTime() + DAY))
|
|
34
|
+
keys.push(d.toISOString().slice(0, 10));
|
|
35
|
+
return keys;
|
|
36
|
+
}
|
|
37
|
+
function fill(keys, rows) {
|
|
38
|
+
const by = new Map(rows.map((r) => [r.k, Number(r.n)]));
|
|
39
|
+
return keys.map((key) => ({ key, value: by.get(key) ?? 0 }));
|
|
40
|
+
}
|
|
41
|
+
/** System overview, analytics and organizations for Administration (see adminScope for who sees what). */
|
|
42
|
+
export class AdminService {
|
|
43
|
+
service;
|
|
44
|
+
constructor(service) {
|
|
45
|
+
this.service = service;
|
|
46
|
+
}
|
|
47
|
+
get prisma() {
|
|
48
|
+
return this.service.prisma;
|
|
49
|
+
}
|
|
50
|
+
get app() {
|
|
51
|
+
return this.service.app;
|
|
52
|
+
}
|
|
53
|
+
/** User entities (not the tables plugins add for their own bookkeeping). */
|
|
54
|
+
entities(scope) {
|
|
55
|
+
const internal = new Set(this.app.plugins.flatMap((p) => p.entities));
|
|
56
|
+
return this.app.entities.filter((e) => !internal.has(e.name) && e.features.timestamps && (scope.global || e.features.tenant));
|
|
57
|
+
}
|
|
58
|
+
userWhere(scope) {
|
|
59
|
+
return scope.organizationId ? { members: { some: { organizationId: scope.organizationId } } } : {};
|
|
60
|
+
}
|
|
61
|
+
/** Accounts created (global) or members who joined the organization, since `since`. */
|
|
62
|
+
async joinedSince(scope, since, until) {
|
|
63
|
+
const range = { gte: since, ...(until ? { lt: until } : {}) };
|
|
64
|
+
if (scope.organizationId)
|
|
65
|
+
return this.prisma.member.count({ where: { organizationId: scope.organizationId, createdAt: range } });
|
|
66
|
+
return this.prisma.user.count({ where: { createdAt: range } });
|
|
67
|
+
}
|
|
68
|
+
async overview(ctx) {
|
|
69
|
+
const scope = adminScope(this.service, ctx);
|
|
70
|
+
const now = new Date();
|
|
71
|
+
const weekAgo = new Date(now.getTime() - 7 * DAY);
|
|
72
|
+
const twoWeeksAgo = new Date(now.getTime() - 14 * DAY);
|
|
73
|
+
const monthAgo = new Date(now.getTime() - 30 * DAY);
|
|
74
|
+
const tenancy = this.app.tenancy.enabled;
|
|
75
|
+
const [usersTotal, newThisWeek, newPreviousWeek, activeSessions] = await Promise.all([
|
|
76
|
+
scope.organizationId ? this.prisma.member.count({ where: { organizationId: scope.organizationId } }) : this.prisma.user.count(),
|
|
77
|
+
this.joinedSince(scope, weekAgo),
|
|
78
|
+
this.joinedSince(scope, twoWeeksAgo, weekAgo),
|
|
79
|
+
this.prisma.session.count({ where: { expiresAt: { gt: now }, ...(scope.organizationId ? { user: this.userWhere(scope) } : {}) } }),
|
|
80
|
+
]);
|
|
81
|
+
const weekly = await this.weeklyJoins(scope, 8);
|
|
82
|
+
const organizations = tenancy && scope.global
|
|
83
|
+
? { total: await this.prisma.organization.count(), newThisWeek: await this.prisma.organization.count({ where: { createdAt: { gte: weekAgo } } }) }
|
|
84
|
+
: null;
|
|
85
|
+
const records = [];
|
|
86
|
+
for (const e of this.entities(scope)) {
|
|
87
|
+
const where = scope.organizationId ? { organizationId: scope.organizationId } : {};
|
|
88
|
+
const delegate = this.service.delegate(e);
|
|
89
|
+
const [total, last30] = await Promise.all([delegate.count({ where }), delegate.count({ where: { ...where, createdAt: { gte: monthAgo } } })]);
|
|
90
|
+
records.push({ entity: e.name, label: e.pluralLabel, path: e.path, total, last30 });
|
|
91
|
+
}
|
|
92
|
+
const recentSignups = scope.organizationId
|
|
93
|
+
? (await this.prisma.member.findMany({
|
|
94
|
+
where: { organizationId: scope.organizationId },
|
|
95
|
+
orderBy: { createdAt: "desc" },
|
|
96
|
+
take: 6,
|
|
97
|
+
include: { user: { select: { id: true, name: true, email: true } } },
|
|
98
|
+
})).map((m) => ({ id: m.user.id, name: m.user.name, email: m.user.email, createdAt: m.createdAt, role: m.role }))
|
|
99
|
+
: (await this.prisma.user.findMany({ orderBy: { createdAt: "desc" }, take: 6, select: { id: true, name: true, email: true, createdAt: true, role: true } })).map((u) => ({ ...u, role: u.role ?? "user" }));
|
|
100
|
+
const auditWhere = tenancy && scope.organizationId ? { organizationId: scope.organizationId } : {};
|
|
101
|
+
const recentAudit = (await this.prisma.auditLog.findMany({ where: auditWhere, orderBy: { createdAt: "desc" }, take: 8, include: { user: { select: { id: true, name: true } } } })).map((l) => ({ id: l.id, entity: this.service.entityByStorageName(l.entity)?.name ?? l.entity, recordId: l.recordId, action: l.action, user: l.user ? { id: l.user.id, name: l.user.name } : null, createdAt: l.createdAt }));
|
|
102
|
+
return {
|
|
103
|
+
scope: scope.global ? "global" : "organization",
|
|
104
|
+
organizationId: scope.organizationId,
|
|
105
|
+
users: { total: usersTotal, newThisWeek, newPreviousWeek, weekly },
|
|
106
|
+
organizations,
|
|
107
|
+
sessions: { active: activeSessions },
|
|
108
|
+
records,
|
|
109
|
+
recentSignups,
|
|
110
|
+
recentAudit,
|
|
111
|
+
jobs: scope.global ? await this.jobs() : null,
|
|
112
|
+
webhooks: await this.webhooks(scope, weekAgo),
|
|
113
|
+
billing: scope.global ? await this.billing() : null,
|
|
114
|
+
support: scope.global && this.app.admin.support ? await this.support() : null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** New accounts (or members) per week, oldest first, for the trend sparkline. */
|
|
118
|
+
async weeklyJoins(scope, weeks) {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const out = [];
|
|
121
|
+
for (let i = weeks - 1; i >= 0; i--) {
|
|
122
|
+
const start = new Date(now - (i + 1) * 7 * DAY);
|
|
123
|
+
const end = new Date(now - i * 7 * DAY);
|
|
124
|
+
out.push({ key: start.toISOString().slice(0, 10), value: await this.joinedSince(scope, start, end) });
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
async jobs() {
|
|
129
|
+
const queue = this.service.shared.get("jobsQueue");
|
|
130
|
+
if (!queue)
|
|
131
|
+
return this.app.plugins.some((p) => p.name === "jobs") ? { failed: 0, active: 0, waiting: 0, delayed: 0 } : null;
|
|
132
|
+
try {
|
|
133
|
+
const c = await queue.getJobCounts("failed", "active", "waiting", "delayed");
|
|
134
|
+
return { failed: c.failed ?? 0, active: c.active ?? 0, waiting: c.waiting ?? 0, delayed: c.delayed ?? 0 };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async webhooks(scope, since) {
|
|
141
|
+
if (!this.app.plugins.some((p) => p.name === "webhooks") || !this.prisma.webhookDelivery)
|
|
142
|
+
return null;
|
|
143
|
+
const where = { createdAt: { gte: since }, ...(scope.organizationId ? { organizationId: scope.organizationId } : {}) };
|
|
144
|
+
const [failed, succeeded, pending] = await Promise.all(["failed", "succeeded", "pending"].map((status) => this.prisma.webhookDelivery.count({ where: { ...where, status } })));
|
|
145
|
+
return { failed, succeeded, pending };
|
|
146
|
+
}
|
|
147
|
+
async billing() {
|
|
148
|
+
const billing = this.service.shared.get("billing");
|
|
149
|
+
return billing?.adminMetrics ? billing.adminMetrics() : null;
|
|
150
|
+
}
|
|
151
|
+
async support() {
|
|
152
|
+
if (!this.prisma.supportTicket)
|
|
153
|
+
return null;
|
|
154
|
+
const [open, pending, unassigned] = await Promise.all([
|
|
155
|
+
this.prisma.supportTicket.count({ where: { status: "open" } }),
|
|
156
|
+
this.prisma.supportTicket.count({ where: { status: "pending" } }),
|
|
157
|
+
this.prisma.supportTicket.count({ where: { status: { not: "closed" }, assigneeId: null } }),
|
|
158
|
+
]);
|
|
159
|
+
return { open, pending, unassigned };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Time series for Administration → Analytics: sign-ups (or members joining), active users (people with
|
|
163
|
+
* a session used that day) and records created per entity. `?days=7|30|90|365` (365 is bucketed by month).
|
|
164
|
+
*/
|
|
165
|
+
async analytics(ctx, query = {}) {
|
|
166
|
+
const scope = adminScope(this.service, ctx);
|
|
167
|
+
const days = [7, 30, 90, 365].includes(Number(query.days)) ? Number(query.days) : 30;
|
|
168
|
+
const by = days > 90 ? "month" : "day";
|
|
169
|
+
const now = new Date();
|
|
170
|
+
const since = by === "month" ? new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 11, 1)) : startOfUtcDay(new Date(now.getTime() - (days - 1) * DAY));
|
|
171
|
+
const keys = bucketKeys(since, by, now);
|
|
172
|
+
const fmt = by === "month" ? "YYYY-MM" : "YYYY-MM-DD";
|
|
173
|
+
const bucket = (col) => `to_char(date_trunc('${by}', ${col}), '${fmt}')`;
|
|
174
|
+
const raw = (sql, ...params) => this.prisma.$queryRawUnsafe(sql, ...params);
|
|
175
|
+
const signups = scope.organizationId
|
|
176
|
+
? await raw(`SELECT ${bucket(`"createdAt"`)} AS k, count(*)::int AS n FROM "member" WHERE "createdAt" >= $1 AND "organizationId" = $2 GROUP BY 1`, since, scope.organizationId)
|
|
177
|
+
: await raw(`SELECT ${bucket(`"createdAt"`)} AS k, count(*)::int AS n FROM "user" WHERE "createdAt" >= $1 GROUP BY 1`, since);
|
|
178
|
+
const active = scope.organizationId
|
|
179
|
+
? await raw(`SELECT ${bucket(`s."updatedAt"`)} AS k, count(DISTINCT s."userId")::int AS n FROM "session" s JOIN "member" m ON m."userId" = s."userId" AND m."organizationId" = $2 WHERE s."updatedAt" >= $1 GROUP BY 1`, since, scope.organizationId)
|
|
180
|
+
: await raw(`SELECT ${bucket(`"updatedAt"`)} AS k, count(DISTINCT "userId")::int AS n FROM "session" WHERE "updatedAt" >= $1 GROUP BY 1`, since);
|
|
181
|
+
const activeTotal = scope.organizationId
|
|
182
|
+
? await raw(`SELECT 'all' AS k, count(DISTINCT s."userId")::int AS n FROM "session" s JOIN "member" m ON m."userId" = s."userId" AND m."organizationId" = $2 WHERE s."updatedAt" >= $1`, since, scope.organizationId)
|
|
183
|
+
: await raw(`SELECT 'all' AS k, count(DISTINCT "userId")::int AS n FROM "session" WHERE "updatedAt" >= $1`, since);
|
|
184
|
+
const records = [];
|
|
185
|
+
for (const e of this.entities(scope)) {
|
|
186
|
+
const rows = scope.organizationId
|
|
187
|
+
? await raw(`SELECT ${bucket(`"created_at"`)} AS k, count(*)::int AS n FROM ${q(e.tableName)} WHERE "created_at" >= $1 AND "organization_id" = $2 GROUP BY 1`, since, scope.organizationId)
|
|
188
|
+
: await raw(`SELECT ${bucket(`"created_at"`)} AS k, count(*)::int AS n FROM ${q(e.tableName)} WHERE "created_at" >= $1 GROUP BY 1`, since);
|
|
189
|
+
const series = fill(keys, rows);
|
|
190
|
+
records.push({ entity: e.name, label: e.pluralLabel, path: e.path, total: series.reduce((s, p) => s + p.value, 0), series });
|
|
191
|
+
}
|
|
192
|
+
const signupSeries = fill(keys, signups);
|
|
193
|
+
return {
|
|
194
|
+
scope: scope.global ? "global" : "organization",
|
|
195
|
+
days,
|
|
196
|
+
by,
|
|
197
|
+
since: since.toISOString(),
|
|
198
|
+
signups: { total: signupSeries.reduce((s, p) => s + p.value, 0), series: signupSeries },
|
|
199
|
+
activeUsers: { total: Number(activeTotal[0]?.n ?? 0), series: fill(keys, active) },
|
|
200
|
+
records,
|
|
201
|
+
topEntities: [...records].sort((a, b) => b.total - a.total).slice(0, 5).map(({ entity, label, path, total }) => ({ entity, label, path, total })),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Every organization with its member count and plan (payments plugin). Global admins only. */
|
|
205
|
+
async organizations(ctx, query = {}) {
|
|
206
|
+
adminScope(this.service, ctx, { globalOnly: true });
|
|
207
|
+
if (!this.app.tenancy.enabled)
|
|
208
|
+
throw new NotFoundException("Organizations are not enabled (multiTenant)");
|
|
209
|
+
const page = Math.max(1, Number(query.page) || 1);
|
|
210
|
+
const pageSize = 25;
|
|
211
|
+
const search = typeof query.search === "string" ? query.search.trim().slice(0, 100) : "";
|
|
212
|
+
const where = search ? { OR: [{ name: { contains: search, mode: "insensitive" } }, { slug: { contains: search, mode: "insensitive" } }] } : {};
|
|
213
|
+
const [total, rows] = await this.prisma.$transaction([
|
|
214
|
+
this.prisma.organization.count({ where }),
|
|
215
|
+
this.prisma.organization.findMany({ where, orderBy: { createdAt: "desc" }, skip: (page - 1) * pageSize, take: pageSize, include: { _count: { select: { members: true } } } }),
|
|
216
|
+
]);
|
|
217
|
+
const plans = await this.plans(rows.map((o) => o.id));
|
|
218
|
+
return {
|
|
219
|
+
data: rows.map((o) => ({ id: o.id, name: o.name, slug: o.slug, logo: o.logo ?? null, createdAt: o.createdAt, members: o._count.members, plan: plans.get(o.id) ?? null })),
|
|
220
|
+
meta: { page, pageSize, total, pages: Math.max(1, Math.ceil(total / pageSize)) },
|
|
221
|
+
billing: this.app.plugins.some((p) => p.name === "payments"),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** One organization: members and their roles, pending invitations, plan and record counts. Global admins only. */
|
|
225
|
+
async organization(id, ctx) {
|
|
226
|
+
adminScope(this.service, ctx, { globalOnly: true });
|
|
227
|
+
if (!this.app.tenancy.enabled)
|
|
228
|
+
throw new NotFoundException("Organizations are not enabled (multiTenant)");
|
|
229
|
+
const org = await this.prisma.organization.findUnique({ where: { id } });
|
|
230
|
+
if (!org)
|
|
231
|
+
throw new NotFoundException("Organization not found");
|
|
232
|
+
const [members, invitations] = await Promise.all([
|
|
233
|
+
this.prisma.member.findMany({ where: { organizationId: id }, orderBy: { createdAt: "asc" }, include: { user: { select: { id: true, name: true, email: true, banned: true } } } }),
|
|
234
|
+
this.prisma.invitation.count({ where: { organizationId: id, status: "pending" } }),
|
|
235
|
+
]);
|
|
236
|
+
const records = [];
|
|
237
|
+
for (const e of this.entities({ global: false, organizationId: id }))
|
|
238
|
+
records.push({ entity: e.name, label: e.pluralLabel, path: e.path, total: await this.service.delegate(e).count({ where: { organizationId: id } }) });
|
|
239
|
+
return {
|
|
240
|
+
id: org.id,
|
|
241
|
+
name: org.name,
|
|
242
|
+
slug: org.slug,
|
|
243
|
+
logo: org.logo ?? null,
|
|
244
|
+
createdAt: org.createdAt,
|
|
245
|
+
plan: (await this.plans([id])).get(id) ?? null,
|
|
246
|
+
members: members.map((m) => ({ id: m.id, userId: m.user.id, name: m.user.name, email: m.user.email, banned: !!m.user.banned, role: m.role, joinedAt: m.createdAt })),
|
|
247
|
+
pendingInvitations: invitations,
|
|
248
|
+
records,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async plans(orgIds) {
|
|
252
|
+
const billing = this.service.shared.get("billing");
|
|
253
|
+
return billing?.plansFor ? billing.plansFor(orgIds) : new Map();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -42,6 +42,45 @@ export declare function setupForge(options: ForgeApiOptions): Promise<{
|
|
|
42
42
|
export declare function backupJob(app: AppIR, service: ForgeService): JobDefinition;
|
|
43
43
|
export declare function createForgeApi(options: ForgeApiOptions): Promise<INestApplication>;
|
|
44
44
|
export declare function createForge(options: ForgeApiOptions): Promise<ForgeApi>;
|
|
45
|
+
/**
|
|
46
|
+
* Tekki Cloud wakes sleeping apps by restoring a VM snapshot; every TCP connection in that memory is dead.
|
|
47
|
+
* Prisma reconnects lazily after $disconnect, plugins reopen theirs in onResume.
|
|
48
|
+
* The SIGUSR2 handlers that call this are only registered when TEKKI=1 (set by tekki-init), so apps elsewhere
|
|
49
|
+
* (e.g. under nodemon, which restarts on SIGUSR2) behave as before. A hung $disconnect is abandoned after
|
|
50
|
+
* `disconnectTimeoutMs` (default 5 s) so plugins still reconnect.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resumeAfterSnapshot(prisma: {
|
|
53
|
+
$disconnect(): Promise<void>;
|
|
54
|
+
}, plugins: {
|
|
55
|
+
runtime: {
|
|
56
|
+
onResume?(ctx: PluginContext): void | Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
ctx: PluginContext;
|
|
59
|
+
}[], { disconnectTimeoutMs }?: {
|
|
60
|
+
disconnectTimeoutMs?: number;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Returns a trigger that runs `fn` at most once at a time: a call during a run schedules exactly one rerun
|
|
64
|
+
* after it finishes, and further calls in the meantime do not queue more.
|
|
65
|
+
*/
|
|
66
|
+
export declare function coalesce(fn: () => Promise<void>): () => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Signals Nest's shutdown hooks listen to. Nest's default list includes SIGUSR2: it closes the app and re-raises
|
|
69
|
+
* the signal, which would kill the API on every wake from a Tekki snapshot. On Tekki (TEKKI=1) SIGUSR2 means
|
|
70
|
+
* "resumed" and belongs to resumeAfterSnapshot; elsewhere the default list (`[]`) is kept, so e.g. nodemon's
|
|
71
|
+
* SIGUSR2 still closes the app gracefully.
|
|
72
|
+
*/
|
|
73
|
+
export declare function shutdownSignals(env?: NodeJS.ProcessEnv): string[];
|
|
74
|
+
/**
|
|
75
|
+
* The API's process signals: Nest's shutdown hooks (see shutdownSignals), plugin onStop on SIGTERM/SIGINT and,
|
|
76
|
+
* on Tekki (TEKKI=1), resumeAfterSnapshot on SIGUSR2. Exported for tests.
|
|
77
|
+
*/
|
|
78
|
+
export declare function handleApiSignals(app: Pick<INestApplication, "enableShutdownHooks">, prisma: {
|
|
79
|
+
$disconnect(): Promise<void>;
|
|
80
|
+
}, plugins: {
|
|
81
|
+
runtime: ForgeApiPlugin;
|
|
82
|
+
ctx: PluginContext;
|
|
83
|
+
}[], env?: NodeJS.ProcessEnv): void;
|
|
45
84
|
export declare function startForgeApi(options: ForgeApiOptions & {
|
|
46
85
|
port?: number;
|
|
47
86
|
host?: string;
|
package/dist/bootstrap.js
CHANGED
|
@@ -5,15 +5,18 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import "reflect-metadata";
|
|
8
|
-
import { Module } from "@nestjs/common";
|
|
8
|
+
import { Module, ShutdownSignal } from "@nestjs/common";
|
|
9
9
|
import { NestFactory } from "@nestjs/core";
|
|
10
10
|
import { SwaggerModule } from "@nestjs/swagger";
|
|
11
|
-
import { toNodeHandler } from "better-auth/node";
|
|
11
|
+
import { fromNodeHeaders, toNodeHandler } from "better-auth/node";
|
|
12
12
|
import express from "express";
|
|
13
13
|
import { FORGE_SERVICE, ResourceController, SystemController } from "./controllers.js";
|
|
14
14
|
import { ForgeExceptionFilter } from "./errors.js";
|
|
15
15
|
import { createBackup, filesFromPrisma, localBackupStore } from "./backup.js";
|
|
16
16
|
import { ForgeService } from "./service.js";
|
|
17
|
+
import { SiteController } from "./site-controller.js";
|
|
18
|
+
import { syncSite } from "./site.js";
|
|
19
|
+
import { maintenanceBlock } from "./settings.js";
|
|
17
20
|
let ForgeRootModule = class ForgeRootModule {
|
|
18
21
|
};
|
|
19
22
|
ForgeRootModule = __decorate([
|
|
@@ -24,7 +27,7 @@ export function forgeModule(service, imports = [], pluginControllers = []) {
|
|
|
24
27
|
module: ForgeRootModule,
|
|
25
28
|
imports,
|
|
26
29
|
// Order matters: fixed system and plugin routes must win over the generic `:resource` routes.
|
|
27
|
-
controllers: [SystemController, ...pluginControllers, ResourceController],
|
|
30
|
+
controllers: [SystemController, ...pluginControllers, SiteController, ResourceController],
|
|
28
31
|
providers: [{ provide: FORGE_SERVICE, useValue: service }],
|
|
29
32
|
exports: [FORGE_SERVICE],
|
|
30
33
|
};
|
|
@@ -96,6 +99,9 @@ export async function createForgeApi(options) {
|
|
|
96
99
|
}
|
|
97
100
|
export async function createForge(options) {
|
|
98
101
|
const { service, plugins } = await setupForge(options);
|
|
102
|
+
// Seed/merge the website from forge.config.ts before serving it (never throws; warns when not migrated yet).
|
|
103
|
+
if (options.app.site)
|
|
104
|
+
await syncSite(options.app, options.prisma);
|
|
99
105
|
const prefix = options.app.api.prefix;
|
|
100
106
|
const pluginImports = plugins.flatMap((p) => p.runtime.imports ?? []);
|
|
101
107
|
const pluginControllers = plugins.flatMap((p) => p.runtime.controllers ?? []);
|
|
@@ -112,6 +118,15 @@ export async function createForge(options) {
|
|
|
112
118
|
for (const path of plugins.flatMap((p) => p.runtime.rawBodyPaths ?? []))
|
|
113
119
|
server.use(`${prefix}${path}`, express.raw({ type: "*/*", limit: "1mb" }));
|
|
114
120
|
server.use(express.json({ limit: "1mb" }));
|
|
121
|
+
// Maintenance mode (Administration → System settings): writes are refused except for global admins.
|
|
122
|
+
const rawPaths = plugins.flatMap((p) => p.runtime.rawBodyPaths ?? []);
|
|
123
|
+
server.use(prefix, (req, res, next) => {
|
|
124
|
+
const ctxReq = req;
|
|
125
|
+
const resolve = () => (ctxReq.forgeContext ??= service.resolveContext(fromNodeHeaders(req.headers), req.ip));
|
|
126
|
+
maintenanceBlock(service.settings, req.method, req.path, resolve, rawPaths)
|
|
127
|
+
.then((blocked) => (blocked ? void res.status(503).json({ statusCode: 503, error: "Service Unavailable", message: blocked.message }) : next()))
|
|
128
|
+
.catch(next);
|
|
129
|
+
});
|
|
115
130
|
if (options.cors?.length)
|
|
116
131
|
app.enableCors({ origin: options.cors, credentials: true });
|
|
117
132
|
app.setGlobalPrefix(prefix.replace(/^\//, ""));
|
|
@@ -129,15 +144,89 @@ export async function createForge(options) {
|
|
|
129
144
|
}
|
|
130
145
|
return { app, service, plugins };
|
|
131
146
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Tekki Cloud wakes sleeping apps by restoring a VM snapshot; every TCP connection in that memory is dead.
|
|
149
|
+
* Prisma reconnects lazily after $disconnect, plugins reopen theirs in onResume.
|
|
150
|
+
* The SIGUSR2 handlers that call this are only registered when TEKKI=1 (set by tekki-init), so apps elsewhere
|
|
151
|
+
* (e.g. under nodemon, which restarts on SIGUSR2) behave as before. A hung $disconnect is abandoned after
|
|
152
|
+
* `disconnectTimeoutMs` (default 5 s) so plugins still reconnect.
|
|
153
|
+
*/
|
|
154
|
+
export async function resumeAfterSnapshot(prisma, plugins, { disconnectTimeoutMs = 5000 } = {}) {
|
|
155
|
+
let timer;
|
|
156
|
+
const timedOut = new Promise((resolve) => {
|
|
157
|
+
timer = setTimeout(() => {
|
|
158
|
+
console.error(`[forge] resume: prisma: $disconnect did not finish within ${disconnectTimeoutMs} ms, continuing`);
|
|
159
|
+
resolve();
|
|
160
|
+
}, disconnectTimeoutMs);
|
|
161
|
+
});
|
|
162
|
+
await Promise.race([prisma.$disconnect().catch((e) => console.error(`[forge] resume: prisma: ${e.message}`)), timedOut]);
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
for (const p of plugins) {
|
|
165
|
+
try {
|
|
166
|
+
await p.runtime.onResume?.(p.ctx);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
console.error(`[forge] resume: plugin failed: ${e.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Returns a trigger that runs `fn` at most once at a time: a call during a run schedules exactly one rerun
|
|
175
|
+
* after it finishes, and further calls in the meantime do not queue more.
|
|
176
|
+
*/
|
|
177
|
+
export function coalesce(fn) {
|
|
178
|
+
let running;
|
|
179
|
+
let rerun = false;
|
|
180
|
+
const run = () => {
|
|
181
|
+
running = fn()
|
|
182
|
+
.catch((e) => console.error(`[forge] resume: ${e.message}`))
|
|
183
|
+
.then(() => {
|
|
184
|
+
if (!rerun)
|
|
185
|
+
return void (running = undefined);
|
|
186
|
+
rerun = false;
|
|
187
|
+
return run();
|
|
188
|
+
});
|
|
189
|
+
return running;
|
|
190
|
+
};
|
|
191
|
+
return () => {
|
|
192
|
+
if (running) {
|
|
193
|
+
rerun = true;
|
|
194
|
+
return running;
|
|
195
|
+
}
|
|
196
|
+
return run();
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Signals Nest's shutdown hooks listen to. Nest's default list includes SIGUSR2: it closes the app and re-raises
|
|
201
|
+
* the signal, which would kill the API on every wake from a Tekki snapshot. On Tekki (TEKKI=1) SIGUSR2 means
|
|
202
|
+
* "resumed" and belongs to resumeAfterSnapshot; elsewhere the default list (`[]`) is kept, so e.g. nodemon's
|
|
203
|
+
* SIGUSR2 still closes the app gracefully.
|
|
204
|
+
*/
|
|
205
|
+
export function shutdownSignals(env = process.env) {
|
|
206
|
+
if (env.TEKKI !== "1")
|
|
207
|
+
return [];
|
|
208
|
+
return Object.values(ShutdownSignal).filter((s) => s !== ShutdownSignal.SIGUSR2);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The API's process signals: Nest's shutdown hooks (see shutdownSignals), plugin onStop on SIGTERM/SIGINT and,
|
|
212
|
+
* on Tekki (TEKKI=1), resumeAfterSnapshot on SIGUSR2. Exported for tests.
|
|
213
|
+
*/
|
|
214
|
+
export function handleApiSignals(app, prisma, plugins, env = process.env) {
|
|
215
|
+
app.enableShutdownHooks(shutdownSignals(env));
|
|
135
216
|
const stop = async () => {
|
|
136
217
|
for (const p of plugins)
|
|
137
218
|
await p.runtime.onStop?.(p.ctx);
|
|
138
219
|
};
|
|
139
220
|
process.once("SIGTERM", () => void stop());
|
|
140
221
|
process.once("SIGINT", () => void stop());
|
|
222
|
+
if (env.TEKKI === "1") {
|
|
223
|
+
const resume = coalesce(() => resumeAfterSnapshot(prisma, plugins));
|
|
224
|
+
process.on("SIGUSR2", () => void resume());
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export async function startForgeApi(options) {
|
|
228
|
+
const { app, plugins } = await createForge(options);
|
|
229
|
+
handleApiSignals(app, options.prisma, plugins);
|
|
141
230
|
const port = options.port ?? Number(process.env.API_PORT ?? process.env.PORT ?? 4000);
|
|
142
231
|
await app.listen(port, options.host ?? "0.0.0.0");
|
|
143
232
|
console.log(`[forge] ${options.app.name} API listening on http://localhost:${port}${options.app.api.prefix}`);
|
|
@@ -186,6 +275,10 @@ export async function startForgeWorker(options) {
|
|
|
186
275
|
};
|
|
187
276
|
process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
|
|
188
277
|
process.once("SIGINT", () => void stop().then(() => process.exit(0)));
|
|
278
|
+
if (process.env.TEKKI === "1") {
|
|
279
|
+
const resume = coalesce(() => resumeAfterSnapshot(options.prisma, [...contexts, ...plugins]));
|
|
280
|
+
process.on("SIGUSR2", () => void resume());
|
|
281
|
+
}
|
|
189
282
|
return { stop };
|
|
190
283
|
}
|
|
191
284
|
//# sourceMappingURL=bootstrap.js.map
|