@nexpress/core 0.2.1 → 0.3.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/dist/{audit-54XLVCWD.js → audit-43OLHR3U.js} +4 -4
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +6 -6
- package/dist/{can-YLUHRJAB.js → can-FKIEV54H.js} +5 -5
- package/dist/{chunk-TFJ4MKPH.js → chunk-26RYBFTF.js} +2 -2
- package/dist/{chunk-PPAS4SZR.js → chunk-2KNG5KMM.js} +2 -2
- package/dist/{chunk-CYCRYBLX.js → chunk-4EPNN4XG.js} +3 -3
- package/dist/{chunk-PPBWRKO2.js → chunk-74CGJJDY.js} +3 -3
- package/dist/{chunk-2YDGE7YX.js → chunk-B7DTNT4O.js} +2 -2
- package/dist/{chunk-CTSQ7BRI.js → chunk-CAS4Z6IN.js} +2 -2
- package/dist/{chunk-BHK3AD3Q.js → chunk-CHQJG4BB.js} +3 -3
- package/dist/{chunk-NUCGHWCF.js → chunk-CKT4QZDC.js} +7 -7
- package/dist/{chunk-RNR7KISG.js → chunk-DPRBHEHI.js} +4 -4
- package/dist/{chunk-473S4TER.js → chunk-DWG3RZH2.js} +4 -4
- package/dist/{chunk-QVJ2HCAX.js → chunk-ELK6AVW5.js} +4 -4
- package/dist/{chunk-RRYDSZXG.js → chunk-EVOBLRHW.js} +6 -6
- package/dist/{chunk-UGQSQO5B.js → chunk-HM46WM45.js} +9 -9
- package/dist/{chunk-2M4DQNHX.js → chunk-HTDDXBMY.js} +29 -29
- package/dist/{chunk-THX3SHYA.js → chunk-L6VG7IK6.js} +3 -3
- package/dist/{chunk-VNIHXQ7W.js → chunk-LN6NTH6E.js} +5 -5
- package/dist/{chunk-RIPHIRPP.js → chunk-ML2E3P3X.js} +3 -3
- package/dist/{chunk-JJL74ZPK.js → chunk-NFHS7CFV.js} +2 -2
- package/dist/{chunk-55FU6WED.js → chunk-PQBJWZ7D.js} +6 -6
- package/dist/{chunk-DK2JBJH7.js → chunk-QBIJZZ5V.js} +3 -3
- package/dist/{chunk-XX4GLFD6.js → chunk-QSJIKNQJ.js} +11 -11
- package/dist/{chunk-OK5HOCQI.js → chunk-QYP6E5FP.js} +4 -4
- package/dist/{chunk-2GWRIV3W.js → chunk-TD4KNSEP.js} +18 -18
- package/dist/{chunk-S27S42QY.js → chunk-TETTWT56.js} +2 -2
- package/dist/{chunk-FZ7O6DWI.js → chunk-U4QCCLAW.js} +2 -2
- package/dist/{chunk-M43PGOQY.js → chunk-X7K5F2UI.js} +474 -474
- package/dist/chunk-X7K5F2UI.js.map +1 -0
- package/dist/community.d.ts +1 -1
- package/dist/community.js +17 -17
- package/dist/{config-QJLI2A5L.js → config-2Y2YGD25.js} +10 -10
- package/dist/db-schema.d.ts +2 -2
- package/dist/db-schema.js +2 -2
- package/dist/db.d.ts +3 -3
- package/dist/db.js +3 -3
- package/dist/{digest-SY42GQSU.js → digest-ZODDTXA2.js} +6 -6
- package/dist/{host-RVFIDFPR.js → host-YSADWU7J.js} +7 -7
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +5 -5
- package/dist/{index-B6-_vr_m.d.ts → index-7_PBSyOq.d.ts} +1 -1
- package/dist/{index-XwP1ET8b.d.ts → index-9IhVtFfA.d.ts} +2 -2
- package/dist/{index-CY55LC0u.d.ts → index-CXuqW_Gl.d.ts} +3 -3
- package/dist/{index-CeiTvwbp.d.ts → index-ClcinJ29.d.ts} +1 -1
- package/dist/index.d.ts +34 -8
- package/dist/index.js +226 -33
- package/dist/index.js.map +1 -1
- package/dist/{job-log-VZXWQUDK.js → job-log-N3IGI4NA.js} +4 -4
- package/dist/jobs.d.ts +2 -2
- package/dist/jobs.js +4 -4
- package/dist/{logger-S7REWDNE.js → logger-2WUTTELV.js} +2 -2
- package/dist/media.d.ts +2 -2
- package/dist/media.js +5 -5
- package/dist/{mentions-2IHFVSHW.js → mentions-NCQR4B72.js} +5 -5
- package/dist/{mutes-EWAE5FZR.js → mutes-FJSSU2JP.js} +5 -5
- package/dist/{notification-prefs-VPJDU7I6.js → notification-prefs-H4HFVCL7.js} +3 -3
- package/dist/observability.js +2 -2
- package/dist/{registry-XIXDEPVI.js → registry-WZVL5HH6.js} +3 -3
- package/dist/reputation-ICIXDGPM.js +11 -0
- package/dist/{scheduled-2AHT3QBN.js → scheduled-F77QCDOG.js} +8 -8
- package/dist/seo.js +7 -7
- package/dist/{settings-FOBIESPB.js → settings-JODDWMDB.js} +3 -3
- package/dist/{strings-VAE47B2C.js → strings-4EWJYDOG.js} +6 -6
- package/dist/{types-TlsbXS0T.d.ts → types-DI3gxsiY.d.ts} +43 -16
- package/package.json +1 -1
- package/dist/chunk-M43PGOQY.js.map +0 -1
- package/dist/reputation-JRL2YQHM.js +0 -11
- /package/dist/{audit-54XLVCWD.js.map → audit-43OLHR3U.js.map} +0 -0
- /package/dist/{can-YLUHRJAB.js.map → can-FKIEV54H.js.map} +0 -0
- /package/dist/{chunk-TFJ4MKPH.js.map → chunk-26RYBFTF.js.map} +0 -0
- /package/dist/{chunk-PPAS4SZR.js.map → chunk-2KNG5KMM.js.map} +0 -0
- /package/dist/{chunk-CYCRYBLX.js.map → chunk-4EPNN4XG.js.map} +0 -0
- /package/dist/{chunk-PPBWRKO2.js.map → chunk-74CGJJDY.js.map} +0 -0
- /package/dist/{chunk-2YDGE7YX.js.map → chunk-B7DTNT4O.js.map} +0 -0
- /package/dist/{chunk-CTSQ7BRI.js.map → chunk-CAS4Z6IN.js.map} +0 -0
- /package/dist/{chunk-BHK3AD3Q.js.map → chunk-CHQJG4BB.js.map} +0 -0
- /package/dist/{chunk-NUCGHWCF.js.map → chunk-CKT4QZDC.js.map} +0 -0
- /package/dist/{chunk-RNR7KISG.js.map → chunk-DPRBHEHI.js.map} +0 -0
- /package/dist/{chunk-473S4TER.js.map → chunk-DWG3RZH2.js.map} +0 -0
- /package/dist/{chunk-QVJ2HCAX.js.map → chunk-ELK6AVW5.js.map} +0 -0
- /package/dist/{chunk-RRYDSZXG.js.map → chunk-EVOBLRHW.js.map} +0 -0
- /package/dist/{chunk-UGQSQO5B.js.map → chunk-HM46WM45.js.map} +0 -0
- /package/dist/{chunk-2M4DQNHX.js.map → chunk-HTDDXBMY.js.map} +0 -0
- /package/dist/{chunk-THX3SHYA.js.map → chunk-L6VG7IK6.js.map} +0 -0
- /package/dist/{chunk-VNIHXQ7W.js.map → chunk-LN6NTH6E.js.map} +0 -0
- /package/dist/{chunk-RIPHIRPP.js.map → chunk-ML2E3P3X.js.map} +0 -0
- /package/dist/{chunk-JJL74ZPK.js.map → chunk-NFHS7CFV.js.map} +0 -0
- /package/dist/{chunk-55FU6WED.js.map → chunk-PQBJWZ7D.js.map} +0 -0
- /package/dist/{chunk-DK2JBJH7.js.map → chunk-QBIJZZ5V.js.map} +0 -0
- /package/dist/{chunk-XX4GLFD6.js.map → chunk-QSJIKNQJ.js.map} +0 -0
- /package/dist/{chunk-OK5HOCQI.js.map → chunk-QYP6E5FP.js.map} +0 -0
- /package/dist/{chunk-2GWRIV3W.js.map → chunk-TD4KNSEP.js.map} +0 -0
- /package/dist/{chunk-S27S42QY.js.map → chunk-TETTWT56.js.map} +0 -0
- /package/dist/{chunk-FZ7O6DWI.js.map → chunk-U4QCCLAW.js.map} +0 -0
- /package/dist/{config-QJLI2A5L.js.map → config-2Y2YGD25.js.map} +0 -0
- /package/dist/{digest-SY42GQSU.js.map → digest-ZODDTXA2.js.map} +0 -0
- /package/dist/{host-RVFIDFPR.js.map → host-YSADWU7J.js.map} +0 -0
- /package/dist/{job-log-VZXWQUDK.js.map → job-log-N3IGI4NA.js.map} +0 -0
- /package/dist/{logger-S7REWDNE.js.map → logger-2WUTTELV.js.map} +0 -0
- /package/dist/{mentions-2IHFVSHW.js.map → mentions-NCQR4B72.js.map} +0 -0
- /package/dist/{mutes-EWAE5FZR.js.map → mutes-FJSSU2JP.js.map} +0 -0
- /package/dist/{notification-prefs-VPJDU7I6.js.map → notification-prefs-H4HFVCL7.js.map} +0 -0
- /package/dist/{registry-XIXDEPVI.js.map → registry-WZVL5HH6.js.map} +0 -0
- /package/dist/{reputation-JRL2YQHM.js.map → reputation-ICIXDGPM.js.map} +0 -0
- /package/dist/{scheduled-2AHT3QBN.js.map → scheduled-F77QCDOG.js.map} +0 -0
- /package/dist/{settings-FOBIESPB.js.map → settings-JODDWMDB.js.map} +0 -0
- /package/dist/{strings-VAE47B2C.js.map → strings-4EWJYDOG.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/db/schema/
|
|
1
|
+
// src/db/schema/community.ts
|
|
2
2
|
import {
|
|
3
3
|
boolean as boolean2,
|
|
4
4
|
index as index3,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
uuid as uuid2
|
|
27
27
|
} from "drizzle-orm/pg-core";
|
|
28
28
|
|
|
29
|
-
// src/db/schema/
|
|
29
|
+
// src/db/schema/system.ts
|
|
30
30
|
import {
|
|
31
31
|
boolean,
|
|
32
32
|
index,
|
|
@@ -40,336 +40,228 @@ import {
|
|
|
40
40
|
unique,
|
|
41
41
|
uuid
|
|
42
42
|
} from "drizzle-orm/pg-core";
|
|
43
|
-
var
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"hidden",
|
|
56
|
-
"deleted"
|
|
43
|
+
var npUserRoleEnum = pgEnum("np_user_role", [
|
|
44
|
+
"admin",
|
|
45
|
+
"editor",
|
|
46
|
+
// 9.5: community moderator. Sits OUTSIDE the linear content-edit
|
|
47
|
+
// hierarchy — a moderator handles community moderation (hide
|
|
48
|
+
// comments, resolve reports, issue bans) but cannot author or edit
|
|
49
|
+
// collection content. ROLE_HIERARCHY in config/types.ts intentionally
|
|
50
|
+
// does not list this role; community-moderation paths check the role
|
|
51
|
+
// explicitly via `principalCan()`.
|
|
52
|
+
"moderator",
|
|
53
|
+
"author",
|
|
54
|
+
"viewer"
|
|
57
55
|
]);
|
|
58
|
-
var
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"thread"
|
|
56
|
+
var npRevisionStatusEnum = pgEnum("np_revision_status", [
|
|
57
|
+
"draft",
|
|
58
|
+
"published",
|
|
59
|
+
"autosave"
|
|
63
60
|
]);
|
|
64
|
-
var
|
|
65
|
-
|
|
61
|
+
var npPasswordResetPurposeEnum = pgEnum("np_password_reset_purpose", ["invite", "reset"]);
|
|
62
|
+
var npUsers = pgTable("np_users", {
|
|
63
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
64
|
+
email: text("email").notNull().unique(),
|
|
65
|
+
password: text("password").notNull(),
|
|
66
|
+
name: text("name").notNull(),
|
|
67
|
+
role: npUserRoleEnum("role").notNull(),
|
|
68
|
+
/**
|
|
69
|
+
* Phase 15.5 — super-admin flag. Bypasses per-site membership
|
|
70
|
+
* checks; the super-admin can manage every site including
|
|
71
|
+
* creating / deleting tenants. The flag is independent of
|
|
72
|
+
* the per-site `role` enum (a super-admin still needs a
|
|
73
|
+
* `role` field for non-multi-site contexts; multi-site
|
|
74
|
+
* permissions check `is_super_admin OR site_membership`).
|
|
75
|
+
*/
|
|
76
|
+
isSuperAdmin: boolean("is_super_admin").default(false).notNull(),
|
|
77
|
+
avatar: uuid("avatar").references(() => npMedia.id),
|
|
78
|
+
loginAttempts: integer("login_attempts").default(0).notNull(),
|
|
79
|
+
lockUntil: timestamp("lock_until", { withTimezone: true, mode: "date" }),
|
|
80
|
+
tokenVersion: integer("token_version").default(0).notNull(),
|
|
81
|
+
passwordResetTokenHash: text("password_reset_token_hash"),
|
|
82
|
+
passwordResetExpiresAt: timestamp("password_reset_expires_at", {
|
|
83
|
+
withTimezone: true,
|
|
84
|
+
mode: "date"
|
|
85
|
+
}),
|
|
86
|
+
passwordResetPurpose: npPasswordResetPurposeEnum("password_reset_purpose"),
|
|
87
|
+
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
88
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
89
|
+
});
|
|
90
|
+
var npSiteMemberships = pgTable(
|
|
91
|
+
"np_site_memberships",
|
|
66
92
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
emailVerified: boolean("email_verified").default(false).notNull(),
|
|
71
|
-
/** Argon2 hash. Nullable so SSO-only members can exist without a password. */
|
|
72
|
-
password: text("password"),
|
|
73
|
-
displayName: text("display_name").notNull(),
|
|
74
|
-
avatar: uuid("avatar").references(() => npMedia.id),
|
|
75
|
-
bio: text("bio"),
|
|
76
|
-
status: npMemberStatusEnum("status").default("pending").notNull(),
|
|
77
|
-
reputation: integer("reputation").default(0).notNull(),
|
|
78
|
-
loginAttempts: integer("login_attempts").default(0).notNull(),
|
|
79
|
-
lockUntil: timestamp("lock_until", { withTimezone: true, mode: "date" }),
|
|
80
|
-
/** Bumped to invalidate every issued JWT (logout-everywhere, password reset). */
|
|
81
|
-
tokenVersion: integer("token_version").default(0).notNull(),
|
|
82
|
-
passwordResetTokenHash: text("password_reset_token_hash"),
|
|
83
|
-
passwordResetExpiresAt: timestamp("password_reset_expires_at", {
|
|
84
|
-
withTimezone: true,
|
|
85
|
-
mode: "date"
|
|
86
|
-
}),
|
|
87
|
-
emailVerifyTokenHash: text("email_verify_token_hash"),
|
|
88
|
-
emailVerifyExpiresAt: timestamp("email_verify_expires_at", {
|
|
89
|
-
withTimezone: true,
|
|
90
|
-
mode: "date"
|
|
91
|
-
}),
|
|
92
|
-
/** Plugin-extensible bag — preferences, custom profile fields, etc. */
|
|
93
|
-
meta: jsonb("meta").$type().default({}).notNull(),
|
|
94
|
-
/**
|
|
95
|
-
* Phase 16.3 — per-member notification preferences. Shape:
|
|
96
|
-
* { disabled?: string[] } — kinds the member opted out of
|
|
97
|
-
* { digest?: "off"|"daily"|"weekly" } — email digest cadence (16.4)
|
|
98
|
-
* Empty default = every kind enabled, no email digest.
|
|
99
|
-
*/
|
|
100
|
-
notificationPrefs: jsonb("notification_prefs").$type().default({}).notNull(),
|
|
93
|
+
siteId: text("site_id").notNull(),
|
|
94
|
+
userId: uuid("user_id").notNull().references(() => npUsers.id, { onDelete: "cascade" }),
|
|
95
|
+
role: npUserRoleEnum("role").notNull(),
|
|
101
96
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
102
97
|
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
103
98
|
},
|
|
104
|
-
(table) => [
|
|
99
|
+
(table) => [primaryKey({ columns: [table.siteId, table.userId] })]
|
|
105
100
|
);
|
|
106
|
-
var
|
|
107
|
-
|
|
108
|
-
memberId: uuid("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
109
|
-
tokenHash: text("token_hash").notNull(),
|
|
110
|
-
userAgent: text("user_agent"),
|
|
111
|
-
ip: text("ip"),
|
|
112
|
-
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
|
|
113
|
-
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
114
|
-
});
|
|
115
|
-
var npMemberIdentities = pgTable(
|
|
116
|
-
"np_member_identities",
|
|
101
|
+
var npUserOAuthIdentities = pgTable(
|
|
102
|
+
"np_user_oauth_identities",
|
|
117
103
|
{
|
|
118
104
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
119
|
-
|
|
105
|
+
userId: uuid("user_id").notNull().references(() => npUsers.id, { onDelete: "cascade" }),
|
|
120
106
|
provider: text("provider").notNull(),
|
|
121
|
-
|
|
122
|
-
email: text("email"),
|
|
107
|
+
providerUserId: text("provider_user_id").notNull(),
|
|
123
108
|
/** Free-form per-provider metadata (avatar URL, scopes granted, etc.). */
|
|
124
109
|
metadata: jsonb("metadata").$type().default({}).notNull(),
|
|
125
110
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
126
111
|
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
127
112
|
},
|
|
128
|
-
(table) =>
|
|
129
|
-
unique("
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
role: text("role").notNull(),
|
|
140
|
-
scopeType: npMemberRoleScopeEnum("scope_type").notNull(),
|
|
141
|
-
/** Nullable for `scope_type='site'`. Otherwise an opaque string id. */
|
|
142
|
-
scopeId: text("scope_id"),
|
|
143
|
-
/**
|
|
144
|
-
* Phase 18 — the tenant the grant applies on. For
|
|
145
|
-
* `scope_type='site'` this column IS the site identifier
|
|
146
|
-
* (`scope_id` stays null because site is the root scope).
|
|
147
|
-
* For category / collection / thread grants, `site_id` says
|
|
148
|
-
* which tenant's category/collection/thread this row
|
|
149
|
-
* targets — the same slug exists on every site.
|
|
150
|
-
*/
|
|
151
|
-
siteId: text("site_id").default("default").notNull(),
|
|
152
|
-
grantedBy: uuid("granted_by").references(() => npUsers.id),
|
|
153
|
-
grantedAt: timestamp("granted_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
154
|
-
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" })
|
|
155
|
-
},
|
|
156
|
-
(table) => [
|
|
157
|
-
// Two indexes mirror the two access patterns: "what can this member
|
|
158
|
-
// do?" (memberId scan) and "who mods this scope?" (scope scan).
|
|
159
|
-
index("np_member_roles_member_idx").on(table.memberId),
|
|
160
|
-
index("np_member_roles_scope_idx").on(table.scopeType, table.scopeId),
|
|
161
|
-
index("np_member_roles_site_idx").on(table.siteId, table.memberId),
|
|
162
|
-
// `scope_id` is null for site-wide grants. NULLS NOT
|
|
163
|
-
// DISTINCT makes two null `scope_id`s collide so the
|
|
164
|
-
// unique constraint enforces "one grant per (member, role,
|
|
165
|
-
// scope, site)." `site_id` widens the key so the same
|
|
166
|
-
// member can hold the same role on different tenants.
|
|
167
|
-
unique("np_member_roles_grant_uq").on(table.memberId, table.role, table.scopeType, table.scopeId, table.siteId).nullsNotDistinct()
|
|
168
|
-
]
|
|
113
|
+
(table) => ({
|
|
114
|
+
providerSubjectUnique: unique("np_user_oauth_identities_provider_subject_unique").on(
|
|
115
|
+
table.provider,
|
|
116
|
+
table.providerUserId
|
|
117
|
+
),
|
|
118
|
+
userProviderUnique: unique("np_user_oauth_identities_user_provider_unique").on(
|
|
119
|
+
table.userId,
|
|
120
|
+
table.provider
|
|
121
|
+
),
|
|
122
|
+
userIdx: index("np_user_oauth_identities_user_idx").on(table.userId)
|
|
123
|
+
})
|
|
169
124
|
);
|
|
170
|
-
var
|
|
171
|
-
"
|
|
125
|
+
var npSessions = pgTable("np_sessions", {
|
|
126
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
127
|
+
userId: uuid("user_id").notNull().references(() => npUsers.id, { onDelete: "cascade" }),
|
|
128
|
+
tokenHash: text("token_hash").notNull(),
|
|
129
|
+
userAgent: text("user_agent"),
|
|
130
|
+
ip: text("ip"),
|
|
131
|
+
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(),
|
|
132
|
+
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
133
|
+
});
|
|
134
|
+
var npRevisions = pgTable(
|
|
135
|
+
"np_revisions",
|
|
172
136
|
{
|
|
173
137
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
bodyHtml: text("body_html").notNull(),
|
|
182
|
-
status: npCommentStatusEnum("status").default("visible").notNull(),
|
|
183
|
-
hiddenByUserId: uuid("hidden_by_user_id").references(() => npUsers.id),
|
|
184
|
-
hiddenByMemberId: uuid("hidden_by_member_id").references(() => npMembers.id),
|
|
185
|
-
hiddenReason: text("hidden_reason"),
|
|
186
|
-
editedAt: timestamp("edited_at", { withTimezone: true, mode: "date" }),
|
|
187
|
-
/**
|
|
188
|
-
* Phase 18 — site this comment belongs to. Filled at insert
|
|
189
|
-
* time from the target document's site (canonical) so a
|
|
190
|
-
* forged request resolver can't smuggle a comment into the
|
|
191
|
-
* wrong site. Defaults to `'default'` for legacy single-
|
|
192
|
-
* tenant rows so the migration backfill is a no-op.
|
|
193
|
-
*/
|
|
194
|
-
siteId: text("site_id").default("default").notNull(),
|
|
138
|
+
collection: text("collection").notNull(),
|
|
139
|
+
documentId: text("document_id").notNull(),
|
|
140
|
+
version: integer("version").notNull(),
|
|
141
|
+
status: npRevisionStatusEnum("status").notNull(),
|
|
142
|
+
snapshot: jsonb("snapshot").$type().notNull(),
|
|
143
|
+
changedFields: text("changed_fields").array().notNull(),
|
|
144
|
+
authorId: uuid("author_id").references(() => npUsers.id),
|
|
195
145
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
196
146
|
},
|
|
197
|
-
(table) =>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
147
|
+
(table) => ({
|
|
148
|
+
documentVersionUnique: unique("np_revisions_document_id_version_unique").on(
|
|
149
|
+
table.documentId,
|
|
150
|
+
table.version
|
|
151
|
+
),
|
|
152
|
+
collectionIdx: index("np_revisions_collection_idx").on(table.collection),
|
|
153
|
+
documentIdIdx: index("np_revisions_document_id_idx").on(table.documentId)
|
|
154
|
+
})
|
|
202
155
|
);
|
|
203
|
-
var
|
|
204
|
-
"
|
|
156
|
+
var npSettings = pgTable(
|
|
157
|
+
"np_settings",
|
|
205
158
|
{
|
|
206
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
207
|
-
targetType: text("target_type").notNull(),
|
|
208
|
-
targetId: uuid("target_id").notNull(),
|
|
209
|
-
memberId: uuid("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
210
|
-
kind: text("kind").notNull(),
|
|
211
|
-
/** Phase 18 — site this reaction belongs to (derived from target). */
|
|
212
159
|
siteId: text("site_id").default("default").notNull(),
|
|
213
|
-
|
|
160
|
+
key: text("key").notNull(),
|
|
161
|
+
value: jsonb("value").$type().notNull(),
|
|
162
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
163
|
+
updatedBy: uuid("updated_by").references(() => npUsers.id)
|
|
214
164
|
},
|
|
215
|
-
(table) => [
|
|
216
|
-
index("np_reactions_target_idx").on(table.targetType, table.targetId),
|
|
217
|
-
index("np_reactions_site_idx").on(table.siteId),
|
|
218
|
-
unique("np_reactions_unique").on(table.targetType, table.targetId, table.memberId, table.kind)
|
|
219
|
-
]
|
|
165
|
+
(table) => [primaryKey({ columns: [table.siteId, table.key] })]
|
|
220
166
|
);
|
|
221
|
-
var
|
|
222
|
-
"
|
|
167
|
+
var npSlugHistory = pgTable(
|
|
168
|
+
"np_slug_history",
|
|
223
169
|
{
|
|
224
170
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
225
|
-
followerId: uuid("follower_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
226
|
-
targetType: text("target_type").notNull(),
|
|
227
|
-
targetId: text("target_id").notNull(),
|
|
228
|
-
/**
|
|
229
|
-
* Phase 18 — site the follow happened on. The same global
|
|
230
|
-
* member can follow on multiple sites and each row scopes
|
|
231
|
-
* to where the click happened (so site-scoped notifications
|
|
232
|
-
* + activity feeds don't leak cross-tenant). The unique
|
|
233
|
-
* key is widened to include site_id so the same follower
|
|
234
|
-
* can have parallel follow rows under different tenants.
|
|
235
|
-
*/
|
|
236
171
|
siteId: text("site_id").default("default").notNull(),
|
|
172
|
+
collection: text("collection").notNull(),
|
|
173
|
+
documentId: text("document_id").notNull(),
|
|
174
|
+
oldSlug: text("old_slug").notNull(),
|
|
175
|
+
newSlug: text("new_slug").notNull(),
|
|
237
176
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
238
177
|
},
|
|
239
178
|
(table) => [
|
|
240
|
-
index("
|
|
241
|
-
index("
|
|
242
|
-
unique("np_follows_unique").on(
|
|
243
|
-
table.followerId,
|
|
244
|
-
table.targetType,
|
|
245
|
-
table.targetId,
|
|
246
|
-
table.siteId
|
|
247
|
-
)
|
|
179
|
+
index("np_slug_history_lookup_idx").on(table.siteId, table.collection, table.oldSlug),
|
|
180
|
+
index("np_slug_history_doc_idx").on(table.siteId, table.collection, table.documentId)
|
|
248
181
|
]
|
|
249
182
|
);
|
|
250
|
-
var
|
|
251
|
-
"
|
|
183
|
+
var npNavigation = pgTable(
|
|
184
|
+
"np_navigation",
|
|
252
185
|
{
|
|
253
|
-
|
|
254
|
-
targetId: uuid("target_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
255
|
-
/**
|
|
256
|
-
* Phase 18 — site the mute applies to. A muter can choose
|
|
257
|
-
* to silence someone on one tenant without affecting their
|
|
258
|
-
* other tenants. PK is widened to include site_id so the
|
|
259
|
-
* same `(member, target)` pair can hold parallel rows per
|
|
260
|
-
* site.
|
|
261
|
-
*/
|
|
186
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
262
187
|
siteId: text("site_id").default("default").notNull(),
|
|
263
|
-
|
|
188
|
+
location: text("location").notNull(),
|
|
189
|
+
items: jsonb("items").$type().notNull(),
|
|
190
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
191
|
+
updatedBy: uuid("updated_by").references(() => npUsers.id)
|
|
264
192
|
},
|
|
265
|
-
(table) => [
|
|
266
|
-
primaryKey({ columns: [table.memberId, table.targetId, table.siteId] }),
|
|
267
|
-
index("np_member_mutes_target_idx").on(table.targetId)
|
|
268
|
-
]
|
|
193
|
+
(table) => [unique("np_navigation_site_location_idx").on(table.siteId, table.location)]
|
|
269
194
|
);
|
|
270
|
-
var
|
|
271
|
-
"
|
|
195
|
+
var npStringOverrides = pgTable(
|
|
196
|
+
"np_string_overrides",
|
|
272
197
|
{
|
|
273
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
274
|
-
memberId: uuid("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
275
|
-
kind: text("kind").notNull(),
|
|
276
|
-
payload: jsonb("payload").$type().default({}).notNull(),
|
|
277
|
-
readAt: timestamp("read_at", { withTimezone: true, mode: "date" }),
|
|
278
|
-
/**
|
|
279
|
-
* Phase 18 — site this notification belongs to. A member
|
|
280
|
-
* who's active on multiple tenants gets one inbox per site
|
|
281
|
-
* (the inbox API filters by current site) so cross-tenant
|
|
282
|
-
* activity doesn't bleed into the wrong site's UI.
|
|
283
|
-
*/
|
|
284
198
|
siteId: text("site_id").default("default").notNull(),
|
|
285
|
-
|
|
199
|
+
locale: text("locale").notNull(),
|
|
200
|
+
key: text("key").notNull(),
|
|
201
|
+
value: text("value"),
|
|
202
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
203
|
+
updatedBy: uuid("updated_by").references(() => npUsers.id)
|
|
286
204
|
},
|
|
287
|
-
(table) => [
|
|
288
|
-
index("np_notifications_inbox_idx").on(table.memberId, table.readAt, table.createdAt),
|
|
289
|
-
index("np_notifications_site_inbox_idx").on(table.siteId, table.memberId, table.readAt)
|
|
290
|
-
]
|
|
205
|
+
(table) => [primaryKey({ columns: [table.siteId, table.locale, table.key] })]
|
|
291
206
|
);
|
|
292
|
-
var
|
|
293
|
-
"
|
|
207
|
+
var npSites = pgTable(
|
|
208
|
+
"np_sites",
|
|
294
209
|
{
|
|
295
|
-
id:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
resolution: text("resolution"),
|
|
304
|
-
/**
|
|
305
|
-
* Phase 18 — site this report belongs to. The mod queue
|
|
306
|
-
* is per-site so a category-mod on tenant A doesn't see
|
|
307
|
-
* tenant B's reports.
|
|
308
|
-
*/
|
|
309
|
-
siteId: text("site_id").default("default").notNull(),
|
|
310
|
-
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
210
|
+
id: text("id").primaryKey(),
|
|
211
|
+
name: text("name").notNull(),
|
|
212
|
+
hostname: text("hostname"),
|
|
213
|
+
description: text("description"),
|
|
214
|
+
settings: jsonb("settings").$type().default({}).notNull(),
|
|
215
|
+
isDefault: boolean("is_default").default(false).notNull(),
|
|
216
|
+
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
217
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
311
218
|
},
|
|
312
|
-
(table) => [
|
|
313
|
-
index("np_reports_queue_idx").on(table.resolvedAt, table.createdAt),
|
|
314
|
-
index("np_reports_target_idx").on(table.targetType, table.targetId),
|
|
315
|
-
index("np_reports_site_queue_idx").on(table.siteId, table.resolvedAt)
|
|
316
|
-
]
|
|
219
|
+
(table) => [unique("np_sites_hostname_idx").on(table.hostname)]
|
|
317
220
|
);
|
|
318
|
-
var
|
|
319
|
-
"
|
|
221
|
+
var npPlugins = pgTable("np_plugins", {
|
|
222
|
+
id: text("id").primaryKey(),
|
|
223
|
+
enabled: boolean("enabled").default(true).notNull(),
|
|
224
|
+
installedAt: timestamp("installed_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
225
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
226
|
+
});
|
|
227
|
+
var NP_GLOBAL_PLUGIN_SITE_ID = "_global_";
|
|
228
|
+
var npPluginStorage = pgTable(
|
|
229
|
+
"np_plugin_storage",
|
|
320
230
|
{
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
targetId: text("target_id"),
|
|
328
|
-
payload: jsonb("payload").$type().default({}).notNull(),
|
|
329
|
-
/**
|
|
330
|
-
* Phase 17 — site-scoped audit. Filled by `recordAuditEvent`
|
|
331
|
-
* from the current request's site (the multi-site resolver).
|
|
332
|
-
* Nullable for events that don't belong to a single site
|
|
333
|
-
* (super-admin actions, background jobs, scripts).
|
|
334
|
-
*/
|
|
335
|
-
siteId: text("site_id"),
|
|
336
|
-
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
231
|
+
pluginId: text("plugin_id").notNull(),
|
|
232
|
+
siteId: text("site_id").default(NP_GLOBAL_PLUGIN_SITE_ID).notNull(),
|
|
233
|
+
key: text("key").notNull(),
|
|
234
|
+
value: jsonb("value").$type().notNull(),
|
|
235
|
+
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }),
|
|
236
|
+
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
337
237
|
},
|
|
338
|
-
(table) =>
|
|
339
|
-
|
|
340
|
-
index("
|
|
341
|
-
index("
|
|
342
|
-
|
|
343
|
-
]
|
|
238
|
+
(table) => ({
|
|
239
|
+
pk: primaryKey({ columns: [table.pluginId, table.siteId, table.key] }),
|
|
240
|
+
pluginIdx: index("np_plugin_storage_plugin_id_idx").on(table.pluginId),
|
|
241
|
+
siteIdx: index("np_plugin_storage_site_idx").on(table.siteId)
|
|
242
|
+
})
|
|
344
243
|
);
|
|
345
|
-
var
|
|
346
|
-
"
|
|
244
|
+
var npWorkerHeartbeats = pgTable("np_worker_heartbeats", {
|
|
245
|
+
id: text("id").primaryKey(),
|
|
246
|
+
status: text("status").default("running").notNull(),
|
|
247
|
+
startedAt: timestamp("started_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
248
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
249
|
+
/** Free-form metadata (worker version, hostname, env). */
|
|
250
|
+
meta: jsonb("meta").$type().default({}).notNull()
|
|
251
|
+
});
|
|
252
|
+
var npJobLogs = pgTable(
|
|
253
|
+
"np_job_logs",
|
|
347
254
|
{
|
|
348
255
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }),
|
|
354
|
-
reason: text("reason"),
|
|
355
|
-
byUserId: uuid("by_user_id").references(() => npUsers.id),
|
|
356
|
-
byMemberId: uuid("by_member_id").references(() => npMembers.id),
|
|
357
|
-
/**
|
|
358
|
-
* Phase 18 — the tenant this ban applies to. Pre-Phase 18
|
|
359
|
-
* `scope_type='site'` rows had `scope_id=null` because
|
|
360
|
-
* "site" was the singular root scope; with multi-tenancy
|
|
361
|
-
* the column tells `assertNotBanned` WHICH site the ban
|
|
362
|
-
* blocks writes on. Category / collection scopes resolve
|
|
363
|
-
* per-site too — the same `posts` collection slug exists
|
|
364
|
-
* on every tenant.
|
|
365
|
-
*/
|
|
366
|
-
siteId: text("site_id").default("default").notNull(),
|
|
256
|
+
jobId: text("job_id").notNull(),
|
|
257
|
+
level: text("level").notNull(),
|
|
258
|
+
message: text("message").notNull(),
|
|
259
|
+
context: jsonb("context").$type(),
|
|
367
260
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
368
261
|
},
|
|
369
262
|
(table) => [
|
|
370
|
-
index("
|
|
371
|
-
index("
|
|
372
|
-
index("np_bans_site_idx").on(table.siteId, table.memberId)
|
|
263
|
+
index("np_job_logs_job_idx").on(table.jobId, table.createdAt),
|
|
264
|
+
index("np_job_logs_created_idx").on(table.createdAt)
|
|
373
265
|
]
|
|
374
266
|
);
|
|
375
267
|
|
|
@@ -445,233 +337,362 @@ var npMediaRefs = pgTable2(
|
|
|
445
337
|
})
|
|
446
338
|
);
|
|
447
339
|
|
|
448
|
-
// src/db/schema/
|
|
449
|
-
var
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
// collection content. ROLE_HIERARCHY in config/types.ts intentionally
|
|
456
|
-
// does not list this role; community-moderation paths check the role
|
|
457
|
-
// explicitly via `principalCan()`.
|
|
458
|
-
"moderator",
|
|
459
|
-
"author",
|
|
460
|
-
"viewer"
|
|
340
|
+
// src/db/schema/community.ts
|
|
341
|
+
var npMemberStatusEnum = pgEnum3("np_member_status", [
|
|
342
|
+
"active",
|
|
343
|
+
"pending",
|
|
344
|
+
"suspended",
|
|
345
|
+
"deleted",
|
|
346
|
+
"imported"
|
|
461
347
|
]);
|
|
462
|
-
var
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
"
|
|
348
|
+
var npBanScopeEnum = pgEnum3("np_ban_scope", ["site", "category", "collection"]);
|
|
349
|
+
var npBanKindEnum = pgEnum3("np_ban_kind", ["temporary", "permanent"]);
|
|
350
|
+
var npCommentStatusEnum = pgEnum3("np_comment_status", [
|
|
351
|
+
"visible",
|
|
352
|
+
"pending",
|
|
353
|
+
"hidden",
|
|
354
|
+
"deleted"
|
|
466
355
|
]);
|
|
467
|
-
var
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
* Phase 15.5 — super-admin flag. Bypasses per-site membership
|
|
476
|
-
* checks; the super-admin can manage every site including
|
|
477
|
-
* creating / deleting tenants. The flag is independent of
|
|
478
|
-
* the per-site `role` enum (a super-admin still needs a
|
|
479
|
-
* `role` field for non-multi-site contexts; multi-site
|
|
480
|
-
* permissions check `is_super_admin OR site_membership`).
|
|
481
|
-
*/
|
|
482
|
-
isSuperAdmin: boolean2("is_super_admin").default(false).notNull(),
|
|
483
|
-
avatar: uuid3("avatar").references(() => npMedia.id),
|
|
484
|
-
loginAttempts: integer3("login_attempts").default(0).notNull(),
|
|
485
|
-
lockUntil: timestamp3("lock_until", { withTimezone: true, mode: "date" }),
|
|
486
|
-
tokenVersion: integer3("token_version").default(0).notNull(),
|
|
487
|
-
passwordResetTokenHash: text3("password_reset_token_hash"),
|
|
488
|
-
passwordResetExpiresAt: timestamp3("password_reset_expires_at", {
|
|
489
|
-
withTimezone: true,
|
|
490
|
-
mode: "date"
|
|
491
|
-
}),
|
|
492
|
-
passwordResetPurpose: npPasswordResetPurposeEnum("password_reset_purpose"),
|
|
493
|
-
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
494
|
-
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
495
|
-
});
|
|
496
|
-
var npSiteMemberships = pgTable3(
|
|
497
|
-
"np_site_memberships",
|
|
356
|
+
var npMemberRoleScopeEnum = pgEnum3("np_member_role_scope", [
|
|
357
|
+
"site",
|
|
358
|
+
"category",
|
|
359
|
+
"collection",
|
|
360
|
+
"thread"
|
|
361
|
+
]);
|
|
362
|
+
var npMembers = pgTable3(
|
|
363
|
+
"np_members",
|
|
498
364
|
{
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
365
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
366
|
+
handle: text3("handle").notNull().unique(),
|
|
367
|
+
email: text3("email").notNull().unique(),
|
|
368
|
+
emailVerified: boolean2("email_verified").default(false).notNull(),
|
|
369
|
+
/** Argon2 hash. Nullable so SSO-only members can exist without a password. */
|
|
370
|
+
password: text3("password"),
|
|
371
|
+
displayName: text3("display_name").notNull(),
|
|
372
|
+
avatar: uuid3("avatar").references(() => npMedia.id),
|
|
373
|
+
bio: text3("bio"),
|
|
374
|
+
status: npMemberStatusEnum("status").default("pending").notNull(),
|
|
375
|
+
reputation: integer3("reputation").default(0).notNull(),
|
|
376
|
+
loginAttempts: integer3("login_attempts").default(0).notNull(),
|
|
377
|
+
lockUntil: timestamp3("lock_until", { withTimezone: true, mode: "date" }),
|
|
378
|
+
/** Bumped to invalidate every issued JWT (logout-everywhere, password reset). */
|
|
379
|
+
tokenVersion: integer3("token_version").default(0).notNull(),
|
|
380
|
+
passwordResetTokenHash: text3("password_reset_token_hash"),
|
|
381
|
+
passwordResetExpiresAt: timestamp3("password_reset_expires_at", {
|
|
382
|
+
withTimezone: true,
|
|
383
|
+
mode: "date"
|
|
384
|
+
}),
|
|
385
|
+
emailVerifyTokenHash: text3("email_verify_token_hash"),
|
|
386
|
+
emailVerifyExpiresAt: timestamp3("email_verify_expires_at", {
|
|
387
|
+
withTimezone: true,
|
|
388
|
+
mode: "date"
|
|
389
|
+
}),
|
|
390
|
+
/** Plugin-extensible bag — preferences, custom profile fields, etc. */
|
|
391
|
+
meta: jsonb3("meta").$type().default({}).notNull(),
|
|
392
|
+
/**
|
|
393
|
+
* Phase 16.3 — per-member notification preferences. Shape:
|
|
394
|
+
* { disabled?: string[] } — kinds the member opted out of
|
|
395
|
+
* { digest?: "off"|"daily"|"weekly" } — email digest cadence (16.4)
|
|
396
|
+
* Empty default = every kind enabled, no email digest.
|
|
397
|
+
*/
|
|
398
|
+
notificationPrefs: jsonb3("notification_prefs").$type().default({}).notNull(),
|
|
502
399
|
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
503
400
|
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
504
401
|
},
|
|
505
|
-
(table) => [
|
|
402
|
+
(table) => [index3("np_members_status_idx").on(table.status)]
|
|
506
403
|
);
|
|
507
|
-
var
|
|
508
|
-
"
|
|
404
|
+
var npMemberSessions = pgTable3("np_member_sessions", {
|
|
405
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
406
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
407
|
+
tokenHash: text3("token_hash").notNull(),
|
|
408
|
+
userAgent: text3("user_agent"),
|
|
409
|
+
ip: text3("ip"),
|
|
410
|
+
expiresAt: timestamp3("expires_at", { withTimezone: true, mode: "date" }).notNull(),
|
|
411
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
412
|
+
});
|
|
413
|
+
var npMemberIdentities = pgTable3(
|
|
414
|
+
"np_member_identities",
|
|
509
415
|
{
|
|
510
416
|
id: uuid3("id").defaultRandom().primaryKey(),
|
|
511
|
-
|
|
417
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
512
418
|
provider: text3("provider").notNull(),
|
|
513
|
-
|
|
419
|
+
subject: text3("subject").notNull(),
|
|
420
|
+
email: text3("email"),
|
|
514
421
|
/** Free-form per-provider metadata (avatar URL, scopes granted, etc.). */
|
|
515
422
|
metadata: jsonb3("metadata").$type().default({}).notNull(),
|
|
516
423
|
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
517
424
|
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
518
425
|
},
|
|
519
|
-
(table) =>
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
userProviderUnique: unique2("np_user_oauth_identities_user_provider_unique").on(
|
|
525
|
-
table.userId,
|
|
526
|
-
table.provider
|
|
527
|
-
),
|
|
528
|
-
userIdx: index3("np_user_oauth_identities_user_idx").on(table.userId)
|
|
529
|
-
})
|
|
426
|
+
(table) => [
|
|
427
|
+
unique2("np_member_identities_provider_subject_uq").on(table.provider, table.subject),
|
|
428
|
+
unique2("np_member_identities_member_provider_uq").on(table.memberId, table.provider),
|
|
429
|
+
index3("np_member_identities_member_idx").on(table.memberId)
|
|
430
|
+
]
|
|
530
431
|
);
|
|
531
|
-
var
|
|
532
|
-
|
|
533
|
-
userId: uuid3("user_id").notNull().references(() => npUsers.id, { onDelete: "cascade" }),
|
|
534
|
-
tokenHash: text3("token_hash").notNull(),
|
|
535
|
-
userAgent: text3("user_agent"),
|
|
536
|
-
ip: text3("ip"),
|
|
537
|
-
expiresAt: timestamp3("expires_at", { withTimezone: true, mode: "date" }).notNull(),
|
|
538
|
-
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
539
|
-
});
|
|
540
|
-
var npRevisions = pgTable3(
|
|
541
|
-
"np_revisions",
|
|
432
|
+
var npMemberRoles = pgTable3(
|
|
433
|
+
"np_member_roles",
|
|
542
434
|
{
|
|
543
435
|
id: uuid3("id").defaultRandom().primaryKey(),
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
436
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
437
|
+
role: text3("role").notNull(),
|
|
438
|
+
scopeType: npMemberRoleScopeEnum("scope_type").notNull(),
|
|
439
|
+
/** Nullable for `scope_type='site'`. Otherwise an opaque string id. */
|
|
440
|
+
scopeId: text3("scope_id"),
|
|
441
|
+
/**
|
|
442
|
+
* Phase 18 — the tenant the grant applies on. For
|
|
443
|
+
* `scope_type='site'` this column IS the site identifier
|
|
444
|
+
* (`scope_id` stays null because site is the root scope).
|
|
445
|
+
* For category / collection / thread grants, `site_id` says
|
|
446
|
+
* which tenant's category/collection/thread this row
|
|
447
|
+
* targets — the same slug exists on every site.
|
|
448
|
+
*/
|
|
449
|
+
siteId: text3("site_id").default("default").notNull(),
|
|
450
|
+
grantedBy: uuid3("granted_by").references(() => npUsers.id),
|
|
451
|
+
grantedAt: timestamp3("granted_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
452
|
+
expiresAt: timestamp3("expires_at", { withTimezone: true, mode: "date" })
|
|
552
453
|
},
|
|
553
|
-
(table) =>
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
),
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
454
|
+
(table) => [
|
|
455
|
+
// Two indexes mirror the two access patterns: "what can this member
|
|
456
|
+
// do?" (memberId scan) and "who mods this scope?" (scope scan).
|
|
457
|
+
index3("np_member_roles_member_idx").on(table.memberId),
|
|
458
|
+
index3("np_member_roles_scope_idx").on(table.scopeType, table.scopeId),
|
|
459
|
+
index3("np_member_roles_site_idx").on(table.siteId, table.memberId),
|
|
460
|
+
// `scope_id` is null for site-wide grants. NULLS NOT
|
|
461
|
+
// DISTINCT makes two null `scope_id`s collide so the
|
|
462
|
+
// unique constraint enforces "one grant per (member, role,
|
|
463
|
+
// scope, site)." `site_id` widens the key so the same
|
|
464
|
+
// member can hold the same role on different tenants.
|
|
465
|
+
unique2("np_member_roles_grant_uq").on(table.memberId, table.role, table.scopeType, table.scopeId, table.siteId).nullsNotDistinct()
|
|
466
|
+
]
|
|
561
467
|
);
|
|
562
|
-
var
|
|
563
|
-
"
|
|
468
|
+
var npComments = pgTable3(
|
|
469
|
+
"np_comments",
|
|
564
470
|
{
|
|
471
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
472
|
+
targetType: text3("target_type").notNull(),
|
|
473
|
+
targetId: uuid3("target_id").notNull(),
|
|
474
|
+
parentId: uuid3("parent_id").references(() => npComments.id, {
|
|
475
|
+
onDelete: "cascade"
|
|
476
|
+
}),
|
|
477
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
478
|
+
bodyMd: text3("body_md").notNull(),
|
|
479
|
+
bodyHtml: text3("body_html").notNull(),
|
|
480
|
+
status: npCommentStatusEnum("status").default("visible").notNull(),
|
|
481
|
+
hiddenByUserId: uuid3("hidden_by_user_id").references(() => npUsers.id),
|
|
482
|
+
hiddenByMemberId: uuid3("hidden_by_member_id").references(() => npMembers.id),
|
|
483
|
+
hiddenReason: text3("hidden_reason"),
|
|
484
|
+
editedAt: timestamp3("edited_at", { withTimezone: true, mode: "date" }),
|
|
485
|
+
/**
|
|
486
|
+
* Phase 18 — site this comment belongs to. Filled at insert
|
|
487
|
+
* time from the target document's site (canonical) so a
|
|
488
|
+
* forged request resolver can't smuggle a comment into the
|
|
489
|
+
* wrong site. Defaults to `'default'` for legacy single-
|
|
490
|
+
* tenant rows so the migration backfill is a no-op.
|
|
491
|
+
*/
|
|
565
492
|
siteId: text3("site_id").default("default").notNull(),
|
|
566
|
-
|
|
567
|
-
value: jsonb3("value").$type().notNull(),
|
|
568
|
-
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
569
|
-
updatedBy: uuid3("updated_by").references(() => npUsers.id)
|
|
493
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
570
494
|
},
|
|
571
|
-
(table) => [
|
|
495
|
+
(table) => [
|
|
496
|
+
index3("np_comments_target_idx").on(table.targetType, table.targetId, table.createdAt),
|
|
497
|
+
index3("np_comments_member_idx").on(table.memberId, table.createdAt),
|
|
498
|
+
index3("np_comments_site_idx").on(table.siteId, table.createdAt)
|
|
499
|
+
]
|
|
572
500
|
);
|
|
573
|
-
var
|
|
574
|
-
"
|
|
501
|
+
var npReactions = pgTable3(
|
|
502
|
+
"np_reactions",
|
|
575
503
|
{
|
|
576
504
|
id: uuid3("id").defaultRandom().primaryKey(),
|
|
505
|
+
targetType: text3("target_type").notNull(),
|
|
506
|
+
targetId: uuid3("target_id").notNull(),
|
|
507
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
508
|
+
kind: text3("kind").notNull(),
|
|
509
|
+
/** Phase 18 — site this reaction belongs to (derived from target). */
|
|
577
510
|
siteId: text3("site_id").default("default").notNull(),
|
|
578
|
-
collection: text3("collection").notNull(),
|
|
579
|
-
documentId: text3("document_id").notNull(),
|
|
580
|
-
oldSlug: text3("old_slug").notNull(),
|
|
581
|
-
newSlug: text3("new_slug").notNull(),
|
|
582
511
|
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
583
512
|
},
|
|
584
513
|
(table) => [
|
|
585
|
-
index3("
|
|
586
|
-
index3("
|
|
514
|
+
index3("np_reactions_target_idx").on(table.targetType, table.targetId),
|
|
515
|
+
index3("np_reactions_site_idx").on(table.siteId),
|
|
516
|
+
unique2("np_reactions_unique").on(table.targetType, table.targetId, table.memberId, table.kind)
|
|
587
517
|
]
|
|
588
518
|
);
|
|
589
|
-
var
|
|
590
|
-
"
|
|
519
|
+
var npFollows = pgTable3(
|
|
520
|
+
"np_follows",
|
|
591
521
|
{
|
|
592
522
|
id: uuid3("id").defaultRandom().primaryKey(),
|
|
523
|
+
followerId: uuid3("follower_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
524
|
+
targetType: text3("target_type").notNull(),
|
|
525
|
+
targetId: text3("target_id").notNull(),
|
|
526
|
+
/**
|
|
527
|
+
* Phase 18 — site the follow happened on. The same global
|
|
528
|
+
* member can follow on multiple sites and each row scopes
|
|
529
|
+
* to where the click happened (so site-scoped notifications
|
|
530
|
+
* + activity feeds don't leak cross-tenant). The unique
|
|
531
|
+
* key is widened to include site_id so the same follower
|
|
532
|
+
* can have parallel follow rows under different tenants.
|
|
533
|
+
*/
|
|
593
534
|
siteId: text3("site_id").default("default").notNull(),
|
|
594
|
-
|
|
595
|
-
items: jsonb3("items").$type().notNull(),
|
|
596
|
-
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
597
|
-
updatedBy: uuid3("updated_by").references(() => npUsers.id)
|
|
535
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
598
536
|
},
|
|
599
|
-
(table) => [
|
|
537
|
+
(table) => [
|
|
538
|
+
index3("np_follows_target_idx").on(table.targetType, table.targetId),
|
|
539
|
+
index3("np_follows_site_idx").on(table.siteId),
|
|
540
|
+
unique2("np_follows_unique").on(
|
|
541
|
+
table.followerId,
|
|
542
|
+
table.targetType,
|
|
543
|
+
table.targetId,
|
|
544
|
+
table.siteId
|
|
545
|
+
)
|
|
546
|
+
]
|
|
600
547
|
);
|
|
601
|
-
var
|
|
602
|
-
"
|
|
548
|
+
var npMemberMutes = pgTable3(
|
|
549
|
+
"np_member_mutes",
|
|
603
550
|
{
|
|
551
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
552
|
+
targetId: uuid3("target_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
553
|
+
/**
|
|
554
|
+
* Phase 18 — site the mute applies to. A muter can choose
|
|
555
|
+
* to silence someone on one tenant without affecting their
|
|
556
|
+
* other tenants. PK is widened to include site_id so the
|
|
557
|
+
* same `(member, target)` pair can hold parallel rows per
|
|
558
|
+
* site.
|
|
559
|
+
*/
|
|
604
560
|
siteId: text3("site_id").default("default").notNull(),
|
|
605
|
-
|
|
606
|
-
key: text3("key").notNull(),
|
|
607
|
-
value: text3("value"),
|
|
608
|
-
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
609
|
-
updatedBy: uuid3("updated_by").references(() => npUsers.id)
|
|
561
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
610
562
|
},
|
|
611
|
-
(table) => [
|
|
563
|
+
(table) => [
|
|
564
|
+
primaryKey2({ columns: [table.memberId, table.targetId, table.siteId] }),
|
|
565
|
+
index3("np_member_mutes_target_idx").on(table.targetId)
|
|
566
|
+
]
|
|
612
567
|
);
|
|
613
|
-
var
|
|
614
|
-
"
|
|
568
|
+
var npNotifications = pgTable3(
|
|
569
|
+
"np_notifications",
|
|
615
570
|
{
|
|
616
|
-
id:
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
571
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
572
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
573
|
+
kind: text3("kind").notNull(),
|
|
574
|
+
payload: jsonb3("payload").$type().default({}).notNull(),
|
|
575
|
+
readAt: timestamp3("read_at", { withTimezone: true, mode: "date" }),
|
|
576
|
+
/**
|
|
577
|
+
* Phase 18 — site this notification belongs to. A member
|
|
578
|
+
* who's active on multiple tenants gets one inbox per site
|
|
579
|
+
* (the inbox API filters by current site) so cross-tenant
|
|
580
|
+
* activity doesn't bleed into the wrong site's UI.
|
|
581
|
+
*/
|
|
582
|
+
siteId: text3("site_id").default("default").notNull(),
|
|
583
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
624
584
|
},
|
|
625
|
-
(table) => [
|
|
585
|
+
(table) => [
|
|
586
|
+
index3("np_notifications_inbox_idx").on(table.memberId, table.readAt, table.createdAt),
|
|
587
|
+
index3("np_notifications_site_inbox_idx").on(table.siteId, table.memberId, table.readAt)
|
|
588
|
+
]
|
|
626
589
|
);
|
|
627
|
-
var
|
|
628
|
-
|
|
629
|
-
enabled: boolean2("enabled").default(true).notNull(),
|
|
630
|
-
installedAt: timestamp3("installed_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
631
|
-
updatedAt: timestamp3("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
632
|
-
});
|
|
633
|
-
var NP_GLOBAL_PLUGIN_SITE_ID = "_global_";
|
|
634
|
-
var npPluginStorage = pgTable3(
|
|
635
|
-
"np_plugin_storage",
|
|
590
|
+
var npReports = pgTable3(
|
|
591
|
+
"np_reports",
|
|
636
592
|
{
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
593
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
594
|
+
reporterId: uuid3("reporter_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
595
|
+
targetType: text3("target_type").notNull(),
|
|
596
|
+
targetId: text3("target_id").notNull(),
|
|
597
|
+
reason: text3("reason").notNull(),
|
|
598
|
+
resolvedAt: timestamp3("resolved_at", { withTimezone: true, mode: "date" }),
|
|
599
|
+
resolvedByUserId: uuid3("resolved_by_user_id").references(() => npUsers.id),
|
|
600
|
+
resolvedByMemberId: uuid3("resolved_by_member_id").references(() => npMembers.id),
|
|
601
|
+
resolution: text3("resolution"),
|
|
602
|
+
/**
|
|
603
|
+
* Phase 18 — site this report belongs to. The mod queue
|
|
604
|
+
* is per-site so a category-mod on tenant A doesn't see
|
|
605
|
+
* tenant B's reports.
|
|
606
|
+
*/
|
|
607
|
+
siteId: text3("site_id").default("default").notNull(),
|
|
608
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
643
609
|
},
|
|
644
|
-
(table) =>
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
610
|
+
(table) => [
|
|
611
|
+
index3("np_reports_queue_idx").on(table.resolvedAt, table.createdAt),
|
|
612
|
+
index3("np_reports_target_idx").on(table.targetType, table.targetId),
|
|
613
|
+
index3("np_reports_site_queue_idx").on(table.siteId, table.resolvedAt)
|
|
614
|
+
]
|
|
649
615
|
);
|
|
650
|
-
var
|
|
651
|
-
|
|
652
|
-
status: text3("status").default("running").notNull(),
|
|
653
|
-
startedAt: timestamp3("started_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
654
|
-
lastSeenAt: timestamp3("last_seen_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
|
|
655
|
-
/** Free-form metadata (worker version, hostname, env). */
|
|
656
|
-
meta: jsonb3("meta").$type().default({}).notNull()
|
|
657
|
-
});
|
|
658
|
-
var npJobLogs = pgTable3(
|
|
659
|
-
"np_job_logs",
|
|
616
|
+
var npAuditEvents = pgTable3(
|
|
617
|
+
"np_audit_events",
|
|
660
618
|
{
|
|
661
619
|
id: uuid3("id").defaultRandom().primaryKey(),
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
620
|
+
actorKind: text3("actor_kind").notNull(),
|
|
621
|
+
actorUserId: uuid3("actor_user_id").references(() => npUsers.id),
|
|
622
|
+
actorMemberId: uuid3("actor_member_id").references(() => npMembers.id),
|
|
623
|
+
action: text3("action").notNull(),
|
|
624
|
+
targetType: text3("target_type"),
|
|
625
|
+
targetId: text3("target_id"),
|
|
626
|
+
payload: jsonb3("payload").$type().default({}).notNull(),
|
|
627
|
+
/**
|
|
628
|
+
* Phase 17 — site-scoped audit. Filled by `recordAuditEvent`
|
|
629
|
+
* from the current request's site (the multi-site resolver).
|
|
630
|
+
* Nullable for events that don't belong to a single site
|
|
631
|
+
* (super-admin actions, background jobs, scripts).
|
|
632
|
+
*/
|
|
633
|
+
siteId: text3("site_id"),
|
|
634
|
+
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
635
|
+
},
|
|
636
|
+
(table) => [
|
|
637
|
+
index3("np_audit_target_idx").on(table.targetType, table.targetId, table.createdAt),
|
|
638
|
+
index3("np_audit_actor_user_idx").on(table.actorUserId, table.createdAt),
|
|
639
|
+
index3("np_audit_actor_member_idx").on(table.actorMemberId, table.createdAt),
|
|
640
|
+
index3("np_audit_site_idx").on(table.siteId, table.createdAt)
|
|
641
|
+
]
|
|
642
|
+
);
|
|
643
|
+
var npBans = pgTable3(
|
|
644
|
+
"np_bans",
|
|
645
|
+
{
|
|
646
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
647
|
+
memberId: uuid3("member_id").notNull().references(() => npMembers.id, { onDelete: "cascade" }),
|
|
648
|
+
scopeType: npBanScopeEnum("scope_type").notNull(),
|
|
649
|
+
scopeId: text3("scope_id"),
|
|
650
|
+
kind: npBanKindEnum("kind").notNull(),
|
|
651
|
+
expiresAt: timestamp3("expires_at", { withTimezone: true, mode: "date" }),
|
|
652
|
+
reason: text3("reason"),
|
|
653
|
+
byUserId: uuid3("by_user_id").references(() => npUsers.id),
|
|
654
|
+
byMemberId: uuid3("by_member_id").references(() => npMembers.id),
|
|
655
|
+
/**
|
|
656
|
+
* Phase 18 — the tenant this ban applies to. Pre-Phase 18
|
|
657
|
+
* `scope_type='site'` rows had `scope_id=null` because
|
|
658
|
+
* "site" was the singular root scope; with multi-tenancy
|
|
659
|
+
* the column tells `assertNotBanned` WHICH site the ban
|
|
660
|
+
* blocks writes on. Category / collection scopes resolve
|
|
661
|
+
* per-site too — the same `posts` collection slug exists
|
|
662
|
+
* on every tenant.
|
|
663
|
+
*/
|
|
664
|
+
siteId: text3("site_id").default("default").notNull(),
|
|
666
665
|
createdAt: timestamp3("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull()
|
|
667
666
|
},
|
|
668
667
|
(table) => [
|
|
669
|
-
index3("
|
|
670
|
-
index3("
|
|
668
|
+
index3("np_bans_member_scope_idx").on(table.memberId, table.scopeType, table.scopeId),
|
|
669
|
+
index3("np_bans_active_idx").on(table.memberId, table.expiresAt),
|
|
670
|
+
index3("np_bans_site_idx").on(table.siteId, table.memberId)
|
|
671
671
|
]
|
|
672
672
|
);
|
|
673
673
|
|
|
674
674
|
export {
|
|
675
|
+
npMemberStatusEnum,
|
|
676
|
+
npBanScopeEnum,
|
|
677
|
+
npBanKindEnum,
|
|
678
|
+
npCommentStatusEnum,
|
|
679
|
+
npMemberRoleScopeEnum,
|
|
680
|
+
npMembers,
|
|
681
|
+
npMemberSessions,
|
|
682
|
+
npMemberIdentities,
|
|
683
|
+
npMemberRoles,
|
|
684
|
+
npComments,
|
|
685
|
+
npReactions,
|
|
686
|
+
npFollows,
|
|
687
|
+
npMemberMutes,
|
|
688
|
+
npNotifications,
|
|
689
|
+
npReports,
|
|
690
|
+
npAuditEvents,
|
|
691
|
+
npBans,
|
|
692
|
+
npMediaStatusEnum,
|
|
693
|
+
npMediaFolders,
|
|
694
|
+
npMedia,
|
|
695
|
+
npMediaRefs,
|
|
675
696
|
npUserRoleEnum,
|
|
676
697
|
npRevisionStatusEnum,
|
|
677
698
|
npPasswordResetPurposeEnum,
|
|
@@ -689,27 +710,6 @@ export {
|
|
|
689
710
|
NP_GLOBAL_PLUGIN_SITE_ID,
|
|
690
711
|
npPluginStorage,
|
|
691
712
|
npWorkerHeartbeats,
|
|
692
|
-
npJobLogs
|
|
693
|
-
npMediaStatusEnum,
|
|
694
|
-
npMediaFolders,
|
|
695
|
-
npMedia,
|
|
696
|
-
npMediaRefs,
|
|
697
|
-
npMemberStatusEnum,
|
|
698
|
-
npBanScopeEnum,
|
|
699
|
-
npBanKindEnum,
|
|
700
|
-
npCommentStatusEnum,
|
|
701
|
-
npMemberRoleScopeEnum,
|
|
702
|
-
npMembers,
|
|
703
|
-
npMemberSessions,
|
|
704
|
-
npMemberIdentities,
|
|
705
|
-
npMemberRoles,
|
|
706
|
-
npComments,
|
|
707
|
-
npReactions,
|
|
708
|
-
npFollows,
|
|
709
|
-
npMemberMutes,
|
|
710
|
-
npNotifications,
|
|
711
|
-
npReports,
|
|
712
|
-
npAuditEvents,
|
|
713
|
-
npBans
|
|
713
|
+
npJobLogs
|
|
714
714
|
};
|
|
715
|
-
//# sourceMappingURL=chunk-
|
|
715
|
+
//# sourceMappingURL=chunk-X7K5F2UI.js.map
|