@newcms/database 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1527 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2695 -0
- package/dist/index.d.ts +2695 -14
- package/dist/index.js +1528 -8
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/dist/cache/index.d.ts +0 -3
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/index.js +0 -2
- package/dist/cache/index.js.map +0 -1
- package/dist/cache/object-cache.d.ts +0 -139
- package/dist/cache/object-cache.d.ts.map +0 -1
- package/dist/cache/object-cache.js +0 -321
- package/dist/cache/object-cache.js.map +0 -1
- package/dist/connection.d.ts +0 -18
- package/dist/connection.d.ts.map +0 -1
- package/dist/connection.js +0 -32
- package/dist/connection.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/repositories/meta-repository.d.ts +0 -71
- package/dist/repositories/meta-repository.d.ts.map +0 -1
- package/dist/repositories/meta-repository.js +0 -176
- package/dist/repositories/meta-repository.js.map +0 -1
- package/dist/repositories/options-repository.d.ts +0 -59
- package/dist/repositories/options-repository.d.ts.map +0 -1
- package/dist/repositories/options-repository.js +0 -222
- package/dist/repositories/options-repository.js.map +0 -1
- package/dist/repositories/post-repository.d.ts +0 -121
- package/dist/repositories/post-repository.d.ts.map +0 -1
- package/dist/repositories/post-repository.js +0 -241
- package/dist/repositories/post-repository.js.map +0 -1
- package/dist/repositories/revision-repository.d.ts +0 -50
- package/dist/repositories/revision-repository.d.ts.map +0 -1
- package/dist/repositories/revision-repository.js +0 -149
- package/dist/repositories/revision-repository.js.map +0 -1
- package/dist/repositories/taxonomy-repository.d.ts +0 -63
- package/dist/repositories/taxonomy-repository.d.ts.map +0 -1
- package/dist/repositories/taxonomy-repository.js +0 -268
- package/dist/repositories/taxonomy-repository.js.map +0 -1
- package/dist/schema/comments.d.ts +0 -369
- package/dist/schema/comments.d.ts.map +0 -1
- package/dist/schema/comments.js +0 -47
- package/dist/schema/comments.js.map +0 -1
- package/dist/schema/index.d.ts +0 -9
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -9
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/links.d.ts +0 -245
- package/dist/schema/links.d.ts.map +0 -1
- package/dist/schema/links.js +0 -17
- package/dist/schema/links.js.map +0 -1
- package/dist/schema/options.d.ts +0 -95
- package/dist/schema/options.d.ts.map +0 -1
- package/dist/schema/options.js +0 -12
- package/dist/schema/options.js.map +0 -1
- package/dist/schema/posts.d.ts +0 -509
- package/dist/schema/posts.d.ts.map +0 -1
- package/dist/schema/posts.js +0 -51
- package/dist/schema/posts.js.map +0 -1
- package/dist/schema/scheduled-events.d.ts +0 -156
- package/dist/schema/scheduled-events.d.ts.map +0 -1
- package/dist/schema/scheduled-events.js +0 -22
- package/dist/schema/scheduled-events.js.map +0 -1
- package/dist/schema/sessions.d.ts +0 -157
- package/dist/schema/sessions.d.ts.map +0 -1
- package/dist/schema/sessions.js +0 -26
- package/dist/schema/sessions.js.map +0 -1
- package/dist/schema/terms.d.ts +0 -343
- package/dist/schema/terms.d.ts.map +0 -1
- package/dist/schema/terms.js +0 -46
- package/dist/schema/terms.js.map +0 -1
- package/dist/schema/users.d.ts +0 -288
- package/dist/schema/users.d.ts.map +0 -1
- package/dist/schema/users.js +0 -30
- package/dist/schema/users.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,1529 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/connection.ts
|
|
8
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
9
|
+
import postgres from "postgres";
|
|
10
|
+
|
|
11
|
+
// src/schema/index.ts
|
|
12
|
+
var schema_exports = {};
|
|
13
|
+
__export(schema_exports, {
|
|
14
|
+
commentmeta: () => commentmeta,
|
|
15
|
+
comments: () => comments,
|
|
16
|
+
links: () => links,
|
|
17
|
+
options: () => options,
|
|
18
|
+
postmeta: () => postmeta,
|
|
19
|
+
posts: () => posts,
|
|
20
|
+
scheduledEvents: () => scheduledEvents,
|
|
21
|
+
sessions: () => sessions,
|
|
22
|
+
termRelationships: () => termRelationships,
|
|
23
|
+
termTaxonomy: () => termTaxonomy,
|
|
24
|
+
termmeta: () => termmeta,
|
|
25
|
+
terms: () => terms,
|
|
26
|
+
usermeta: () => usermeta,
|
|
27
|
+
users: () => users
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/schema/posts.ts
|
|
31
|
+
import {
|
|
32
|
+
pgTable as pgTable2,
|
|
33
|
+
serial as serial2,
|
|
34
|
+
bigint,
|
|
35
|
+
text as text2,
|
|
36
|
+
timestamp as timestamp2,
|
|
37
|
+
varchar as varchar2,
|
|
38
|
+
index as index2,
|
|
39
|
+
jsonb as jsonb2
|
|
40
|
+
} from "drizzle-orm/pg-core";
|
|
41
|
+
|
|
42
|
+
// src/schema/users.ts
|
|
43
|
+
import {
|
|
44
|
+
pgTable,
|
|
45
|
+
serial,
|
|
46
|
+
text,
|
|
47
|
+
timestamp,
|
|
48
|
+
varchar,
|
|
49
|
+
index,
|
|
50
|
+
jsonb
|
|
51
|
+
} from "drizzle-orm/pg-core";
|
|
52
|
+
var users = pgTable(
|
|
53
|
+
"users",
|
|
54
|
+
{
|
|
55
|
+
id: serial("id").primaryKey(),
|
|
56
|
+
userLogin: varchar("user_login", { length: 60 }).notNull().unique(),
|
|
57
|
+
userPass: varchar("user_pass", { length: 255 }).notNull().default(""),
|
|
58
|
+
userNicename: varchar("user_nicename", { length: 50 }).notNull().default(""),
|
|
59
|
+
userEmail: varchar("user_email", { length: 100 }).notNull().unique(),
|
|
60
|
+
userUrl: varchar("user_url", { length: 100 }).notNull().default(""),
|
|
61
|
+
userRegistered: timestamp("user_registered", { withTimezone: true }).notNull().defaultNow(),
|
|
62
|
+
userActivationKey: varchar("user_activation_key", { length: 255 }).notNull().default(""),
|
|
63
|
+
userStatus: varchar("user_status", { length: 20 }).notNull().default("active"),
|
|
64
|
+
displayName: varchar("display_name", { length: 250 }).notNull().default("")
|
|
65
|
+
},
|
|
66
|
+
(table) => [
|
|
67
|
+
index("idx_user_login").on(table.userLogin),
|
|
68
|
+
index("idx_user_nicename").on(table.userNicename),
|
|
69
|
+
index("idx_user_email").on(table.userEmail)
|
|
70
|
+
]
|
|
71
|
+
);
|
|
72
|
+
var usermeta = pgTable(
|
|
73
|
+
"usermeta",
|
|
74
|
+
{
|
|
75
|
+
umetaId: serial("umeta_id").primaryKey(),
|
|
76
|
+
userId: serial("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
77
|
+
metaKey: varchar("meta_key", { length: 255 }),
|
|
78
|
+
metaValue: text("meta_value"),
|
|
79
|
+
metaValueJson: jsonb("meta_value_json")
|
|
80
|
+
},
|
|
81
|
+
(table) => [
|
|
82
|
+
index("idx_usermeta_user_id").on(table.userId),
|
|
83
|
+
index("idx_usermeta_meta_key").on(table.metaKey)
|
|
84
|
+
]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// src/schema/posts.ts
|
|
88
|
+
var posts = pgTable2(
|
|
89
|
+
"posts",
|
|
90
|
+
{
|
|
91
|
+
id: serial2("id").primaryKey(),
|
|
92
|
+
postAuthor: bigint("post_author", { mode: "number" }).notNull().default(0).references(() => users.id),
|
|
93
|
+
postDate: timestamp2("post_date", { withTimezone: true }).notNull().defaultNow(),
|
|
94
|
+
postDateGmt: timestamp2("post_date_gmt", { withTimezone: true }).notNull().defaultNow(),
|
|
95
|
+
postContent: text2("post_content").notNull().default(""),
|
|
96
|
+
postTitle: text2("post_title").notNull().default(""),
|
|
97
|
+
postExcerpt: text2("post_excerpt").notNull().default(""),
|
|
98
|
+
postStatus: varchar2("post_status", { length: 20 }).notNull().default("publish"),
|
|
99
|
+
commentStatus: varchar2("comment_status", { length: 20 }).notNull().default("open"),
|
|
100
|
+
pingStatus: varchar2("ping_status", { length: 20 }).notNull().default("open"),
|
|
101
|
+
postPassword: varchar2("post_password", { length: 255 }).notNull().default(""),
|
|
102
|
+
postName: varchar2("post_name", { length: 200 }).notNull().default(""),
|
|
103
|
+
toPing: text2("to_ping").notNull().default(""),
|
|
104
|
+
pinged: text2("pinged").notNull().default(""),
|
|
105
|
+
postModified: timestamp2("post_modified", { withTimezone: true }).notNull().defaultNow(),
|
|
106
|
+
postModifiedGmt: timestamp2("post_modified_gmt", { withTimezone: true }).notNull().defaultNow(),
|
|
107
|
+
postContentFiltered: text2("post_content_filtered").notNull().default(""),
|
|
108
|
+
postParent: bigint("post_parent", { mode: "number" }).notNull().default(0),
|
|
109
|
+
guid: varchar2("guid", { length: 255 }).notNull().default(""),
|
|
110
|
+
menuOrder: bigint("menu_order", { mode: "number" }).notNull().default(0),
|
|
111
|
+
postType: varchar2("post_type", { length: 20 }).notNull().default("post"),
|
|
112
|
+
postMimeType: varchar2("post_mime_type", { length: 100 }).notNull().default(""),
|
|
113
|
+
commentCount: bigint("comment_count", { mode: "number" }).notNull().default(0)
|
|
114
|
+
},
|
|
115
|
+
(table) => [
|
|
116
|
+
index2("idx_post_name").on(table.postName),
|
|
117
|
+
index2("idx_post_type_status_date").on(table.postType, table.postStatus, table.postDate),
|
|
118
|
+
index2("idx_post_parent").on(table.postParent),
|
|
119
|
+
index2("idx_post_author").on(table.postAuthor)
|
|
120
|
+
]
|
|
121
|
+
);
|
|
122
|
+
var postmeta = pgTable2(
|
|
123
|
+
"postmeta",
|
|
124
|
+
{
|
|
125
|
+
metaId: serial2("meta_id").primaryKey(),
|
|
126
|
+
postId: bigint("post_id", { mode: "number" }).notNull().references(() => posts.id, { onDelete: "cascade" }),
|
|
127
|
+
metaKey: varchar2("meta_key", { length: 255 }),
|
|
128
|
+
metaValue: text2("meta_value"),
|
|
129
|
+
metaValueJson: jsonb2("meta_value_json")
|
|
130
|
+
},
|
|
131
|
+
(table) => [
|
|
132
|
+
index2("idx_postmeta_post_id").on(table.postId),
|
|
133
|
+
index2("idx_postmeta_meta_key").on(table.metaKey),
|
|
134
|
+
index2("idx_postmeta_post_key").on(table.postId, table.metaKey)
|
|
135
|
+
]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// src/schema/comments.ts
|
|
139
|
+
import {
|
|
140
|
+
pgTable as pgTable3,
|
|
141
|
+
serial as serial3,
|
|
142
|
+
bigint as bigint2,
|
|
143
|
+
text as text3,
|
|
144
|
+
timestamp as timestamp3,
|
|
145
|
+
varchar as varchar3,
|
|
146
|
+
index as index3,
|
|
147
|
+
jsonb as jsonb3
|
|
148
|
+
} from "drizzle-orm/pg-core";
|
|
149
|
+
var comments = pgTable3(
|
|
150
|
+
"comments",
|
|
151
|
+
{
|
|
152
|
+
commentId: serial3("comment_id").primaryKey(),
|
|
153
|
+
commentPostId: bigint2("comment_post_id", { mode: "number" }).notNull().default(0).references(() => posts.id, { onDelete: "cascade" }),
|
|
154
|
+
commentAuthor: text3("comment_author").notNull().default(""),
|
|
155
|
+
commentAuthorEmail: varchar3("comment_author_email", { length: 100 }).notNull().default(""),
|
|
156
|
+
commentAuthorUrl: varchar3("comment_author_url", { length: 200 }).notNull().default(""),
|
|
157
|
+
commentAuthorIp: varchar3("comment_author_ip", { length: 100 }).notNull().default(""),
|
|
158
|
+
commentDate: timestamp3("comment_date", { withTimezone: true }).notNull().defaultNow(),
|
|
159
|
+
commentDateGmt: timestamp3("comment_date_gmt", { withTimezone: true }).notNull().defaultNow(),
|
|
160
|
+
commentContent: text3("comment_content").notNull().default(""),
|
|
161
|
+
commentKarma: bigint2("comment_karma", { mode: "number" }).notNull().default(0),
|
|
162
|
+
commentApproved: varchar3("comment_approved", { length: 20 }).notNull().default("1"),
|
|
163
|
+
commentAgent: varchar3("comment_agent", { length: 255 }).notNull().default(""),
|
|
164
|
+
commentType: varchar3("comment_type", { length: 20 }).notNull().default("comment"),
|
|
165
|
+
commentParent: bigint2("comment_parent", { mode: "number" }).notNull().default(0),
|
|
166
|
+
userId: bigint2("user_id", { mode: "number" }).notNull().default(0).references(() => users.id)
|
|
167
|
+
},
|
|
168
|
+
(table) => [
|
|
169
|
+
index3("idx_comment_post_id").on(table.commentPostId),
|
|
170
|
+
index3("idx_comment_approved_date_gmt").on(table.commentApproved, table.commentDateGmt),
|
|
171
|
+
index3("idx_comment_date_gmt").on(table.commentDateGmt),
|
|
172
|
+
index3("idx_comment_parent").on(table.commentParent),
|
|
173
|
+
index3("idx_comment_author_email").on(table.commentAuthorEmail)
|
|
174
|
+
]
|
|
175
|
+
);
|
|
176
|
+
var commentmeta = pgTable3(
|
|
177
|
+
"commentmeta",
|
|
178
|
+
{
|
|
179
|
+
metaId: serial3("meta_id").primaryKey(),
|
|
180
|
+
commentId: bigint2("comment_id", { mode: "number" }).notNull().references(() => comments.commentId, { onDelete: "cascade" }),
|
|
181
|
+
metaKey: varchar3("meta_key", { length: 255 }),
|
|
182
|
+
metaValue: text3("meta_value"),
|
|
183
|
+
metaValueJson: jsonb3("meta_value_json")
|
|
184
|
+
},
|
|
185
|
+
(table) => [
|
|
186
|
+
index3("idx_commentmeta_comment_id").on(table.commentId),
|
|
187
|
+
index3("idx_commentmeta_meta_key").on(table.metaKey)
|
|
188
|
+
]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// src/schema/terms.ts
|
|
192
|
+
import {
|
|
193
|
+
pgTable as pgTable4,
|
|
194
|
+
serial as serial4,
|
|
195
|
+
bigint as bigint3,
|
|
196
|
+
text as text4,
|
|
197
|
+
varchar as varchar4,
|
|
198
|
+
index as index4,
|
|
199
|
+
jsonb as jsonb4,
|
|
200
|
+
uniqueIndex,
|
|
201
|
+
primaryKey
|
|
202
|
+
} from "drizzle-orm/pg-core";
|
|
203
|
+
var terms = pgTable4(
|
|
204
|
+
"terms",
|
|
205
|
+
{
|
|
206
|
+
termId: serial4("term_id").primaryKey(),
|
|
207
|
+
name: varchar4("name", { length: 200 }).notNull().default(""),
|
|
208
|
+
slug: varchar4("slug", { length: 200 }).notNull().default(""),
|
|
209
|
+
termGroup: bigint3("term_group", { mode: "number" }).notNull().default(0)
|
|
210
|
+
},
|
|
211
|
+
(table) => [index4("idx_term_slug").on(table.slug), index4("idx_term_name").on(table.name)]
|
|
212
|
+
);
|
|
213
|
+
var termTaxonomy = pgTable4(
|
|
214
|
+
"term_taxonomy",
|
|
215
|
+
{
|
|
216
|
+
termTaxonomyId: serial4("term_taxonomy_id").primaryKey(),
|
|
217
|
+
termId: bigint3("term_id", { mode: "number" }).notNull().references(() => terms.termId, { onDelete: "cascade" }),
|
|
218
|
+
taxonomy: varchar4("taxonomy", { length: 32 }).notNull().default(""),
|
|
219
|
+
description: text4("description").notNull().default(""),
|
|
220
|
+
parent: bigint3("parent", { mode: "number" }).notNull().default(0),
|
|
221
|
+
count: bigint3("count", { mode: "number" }).notNull().default(0)
|
|
222
|
+
},
|
|
223
|
+
(table) => [
|
|
224
|
+
uniqueIndex("idx_term_id_taxonomy").on(table.termId, table.taxonomy),
|
|
225
|
+
index4("idx_taxonomy").on(table.taxonomy)
|
|
226
|
+
]
|
|
227
|
+
);
|
|
228
|
+
var termRelationships = pgTable4(
|
|
229
|
+
"term_relationships",
|
|
230
|
+
{
|
|
231
|
+
objectId: bigint3("object_id", { mode: "number" }).notNull().references(() => posts.id, { onDelete: "cascade" }),
|
|
232
|
+
termTaxonomyId: bigint3("term_taxonomy_id", { mode: "number" }).notNull().references(() => termTaxonomy.termTaxonomyId, { onDelete: "cascade" }),
|
|
233
|
+
termOrder: bigint3("term_order", { mode: "number" }).notNull().default(0)
|
|
234
|
+
},
|
|
235
|
+
(table) => [
|
|
236
|
+
primaryKey({ columns: [table.objectId, table.termTaxonomyId] }),
|
|
237
|
+
index4("idx_term_taxonomy_id").on(table.termTaxonomyId)
|
|
238
|
+
]
|
|
239
|
+
);
|
|
240
|
+
var termmeta = pgTable4(
|
|
241
|
+
"termmeta",
|
|
242
|
+
{
|
|
243
|
+
metaId: serial4("meta_id").primaryKey(),
|
|
244
|
+
termId: bigint3("term_id", { mode: "number" }).notNull().references(() => terms.termId, { onDelete: "cascade" }),
|
|
245
|
+
metaKey: varchar4("meta_key", { length: 255 }),
|
|
246
|
+
metaValue: text4("meta_value"),
|
|
247
|
+
metaValueJson: jsonb4("meta_value_json")
|
|
248
|
+
},
|
|
249
|
+
(table) => [
|
|
250
|
+
index4("idx_termmeta_term_id").on(table.termId),
|
|
251
|
+
index4("idx_termmeta_meta_key").on(table.metaKey)
|
|
252
|
+
]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// src/schema/options.ts
|
|
256
|
+
import {
|
|
257
|
+
pgTable as pgTable5,
|
|
258
|
+
serial as serial5,
|
|
259
|
+
text as text5,
|
|
260
|
+
varchar as varchar5,
|
|
261
|
+
boolean,
|
|
262
|
+
jsonb as jsonb5,
|
|
263
|
+
uniqueIndex as uniqueIndex2,
|
|
264
|
+
index as index5
|
|
265
|
+
} from "drizzle-orm/pg-core";
|
|
266
|
+
var options = pgTable5(
|
|
267
|
+
"options",
|
|
268
|
+
{
|
|
269
|
+
optionId: serial5("option_id").primaryKey(),
|
|
270
|
+
optionName: varchar5("option_name", { length: 191 }).notNull().unique(),
|
|
271
|
+
optionValue: text5("option_value").notNull().default(""),
|
|
272
|
+
optionValueJson: jsonb5("option_value_json"),
|
|
273
|
+
autoload: boolean("autoload").notNull().default(true)
|
|
274
|
+
},
|
|
275
|
+
(table) => [
|
|
276
|
+
uniqueIndex2("idx_option_name").on(table.optionName),
|
|
277
|
+
index5("idx_autoload").on(table.autoload)
|
|
278
|
+
]
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// src/schema/links.ts
|
|
282
|
+
import {
|
|
283
|
+
pgTable as pgTable6,
|
|
284
|
+
serial as serial6,
|
|
285
|
+
text as text6,
|
|
286
|
+
timestamp as timestamp4,
|
|
287
|
+
varchar as varchar6,
|
|
288
|
+
bigint as bigint4,
|
|
289
|
+
index as index6
|
|
290
|
+
} from "drizzle-orm/pg-core";
|
|
291
|
+
var links = pgTable6(
|
|
292
|
+
"links",
|
|
293
|
+
{
|
|
294
|
+
linkId: serial6("link_id").primaryKey(),
|
|
295
|
+
linkUrl: varchar6("link_url", { length: 255 }).notNull().default(""),
|
|
296
|
+
linkName: varchar6("link_name", { length: 255 }).notNull().default(""),
|
|
297
|
+
linkImage: varchar6("link_image", { length: 255 }).notNull().default(""),
|
|
298
|
+
linkTarget: varchar6("link_target", { length: 25 }).notNull().default(""),
|
|
299
|
+
linkDescription: varchar6("link_description", { length: 255 }).notNull().default(""),
|
|
300
|
+
linkVisible: varchar6("link_visible", { length: 20 }).notNull().default("Y"),
|
|
301
|
+
linkOwner: bigint4("link_owner", { mode: "number" }).notNull().default(1),
|
|
302
|
+
linkRating: bigint4("link_rating", { mode: "number" }).notNull().default(0),
|
|
303
|
+
linkUpdated: timestamp4("link_updated", { withTimezone: true }).notNull().defaultNow(),
|
|
304
|
+
linkRel: varchar6("link_rel", { length: 255 }).notNull().default(""),
|
|
305
|
+
linkNotes: text6("link_notes").notNull().default(""),
|
|
306
|
+
linkRss: varchar6("link_rss", { length: 255 }).notNull().default("")
|
|
307
|
+
},
|
|
308
|
+
(table) => [index6("idx_link_visible").on(table.linkVisible)]
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// src/schema/sessions.ts
|
|
312
|
+
import { pgTable as pgTable7, serial as serial7, bigint as bigint5, varchar as varchar7, timestamp as timestamp5, jsonb as jsonb6, index as index7 } from "drizzle-orm/pg-core";
|
|
313
|
+
var sessions = pgTable7(
|
|
314
|
+
"sessions",
|
|
315
|
+
{
|
|
316
|
+
id: serial7("id").primaryKey(),
|
|
317
|
+
userId: bigint5("user_id", { mode: "number" }).notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
318
|
+
tokenHash: varchar7("token_hash", { length: 255 }).notNull().unique(),
|
|
319
|
+
createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
320
|
+
expiresAt: timestamp5("expires_at", { withTimezone: true }).notNull(),
|
|
321
|
+
ip: varchar7("ip", { length: 45 }).notNull().default(""),
|
|
322
|
+
userAgent: varchar7("user_agent", { length: 500 }).notNull().default(""),
|
|
323
|
+
data: jsonb6("data")
|
|
324
|
+
},
|
|
325
|
+
(table) => [
|
|
326
|
+
index7("idx_session_user_id").on(table.userId),
|
|
327
|
+
index7("idx_session_token_hash").on(table.tokenHash),
|
|
328
|
+
index7("idx_session_expires_at").on(table.expiresAt)
|
|
329
|
+
]
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// src/schema/scheduled-events.ts
|
|
333
|
+
import { pgTable as pgTable8, serial as serial8, varchar as varchar8, timestamp as timestamp6, jsonb as jsonb7, bigint as bigint6, index as index8 } from "drizzle-orm/pg-core";
|
|
334
|
+
var scheduledEvents = pgTable8(
|
|
335
|
+
"scheduled_events",
|
|
336
|
+
{
|
|
337
|
+
id: serial8("id").primaryKey(),
|
|
338
|
+
hook: varchar8("hook", { length: 255 }).notNull(),
|
|
339
|
+
args: jsonb7("args").notNull().default("[]"),
|
|
340
|
+
schedule: varchar8("schedule", { length: 50 }),
|
|
341
|
+
intervalSeconds: bigint6("interval_seconds", { mode: "number" }),
|
|
342
|
+
nextRunAt: timestamp6("next_run_at", { withTimezone: true }).notNull(),
|
|
343
|
+
createdAt: timestamp6("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
344
|
+
status: varchar8("status", { length: 20 }).notNull().default("pending")
|
|
345
|
+
},
|
|
346
|
+
(table) => [
|
|
347
|
+
index8("idx_scheduled_hook").on(table.hook),
|
|
348
|
+
index8("idx_scheduled_next_run").on(table.nextRunAt),
|
|
349
|
+
index8("idx_scheduled_status").on(table.status)
|
|
350
|
+
]
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// src/connection.ts
|
|
354
|
+
function getConfigFromEnv() {
|
|
355
|
+
const password = process.env["DB_PASSWORD"];
|
|
356
|
+
if (!password) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
"DB_PASSWORD environment variable is required. Set it in your .env file or environment."
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
host: process.env["DB_HOST"] ?? "localhost",
|
|
363
|
+
port: parseInt(process.env["DB_PORT"] ?? "5432", 10),
|
|
364
|
+
database: process.env["DB_NAME"] ?? "newcms",
|
|
365
|
+
user: process.env["DB_USER"] ?? "newcms",
|
|
366
|
+
password,
|
|
367
|
+
maxConnections: parseInt(process.env["DB_MAX_CONNECTIONS"] ?? "10", 10)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function createConnection(config) {
|
|
371
|
+
const cfg = config ?? getConfigFromEnv();
|
|
372
|
+
const client = postgres({
|
|
373
|
+
host: cfg.host,
|
|
374
|
+
port: cfg.port,
|
|
375
|
+
database: cfg.database,
|
|
376
|
+
user: cfg.user,
|
|
377
|
+
password: cfg.password,
|
|
378
|
+
max: cfg.maxConnections ?? 10
|
|
379
|
+
});
|
|
380
|
+
const db = drizzle(client, { schema: schema_exports });
|
|
381
|
+
return { db, client };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/cache/object-cache.ts
|
|
385
|
+
import Redis from "ioredis";
|
|
386
|
+
var ObjectCache = class {
|
|
387
|
+
redis;
|
|
388
|
+
siteId = 1;
|
|
389
|
+
keyPrefix;
|
|
390
|
+
defaultTtl;
|
|
391
|
+
/** Groups that are shared across all sites (multisite) */
|
|
392
|
+
globalGroups = /* @__PURE__ */ new Set();
|
|
393
|
+
/** Per-group TTL overrides (in seconds) */
|
|
394
|
+
groupTtl = /* @__PURE__ */ new Map();
|
|
395
|
+
/** Local in-memory cache for the current request (non-persistent) */
|
|
396
|
+
localCache = /* @__PURE__ */ new Map();
|
|
397
|
+
constructor(config) {
|
|
398
|
+
this.redis = new Redis({
|
|
399
|
+
host: config.host,
|
|
400
|
+
port: config.port,
|
|
401
|
+
lazyConnect: true,
|
|
402
|
+
maxRetriesPerRequest: 3
|
|
403
|
+
});
|
|
404
|
+
this.keyPrefix = config.keyPrefix ?? "cache";
|
|
405
|
+
this.defaultTtl = config.defaultTtl ?? 0;
|
|
406
|
+
}
|
|
407
|
+
async connect() {
|
|
408
|
+
await this.redis.connect();
|
|
409
|
+
}
|
|
410
|
+
async disconnect() {
|
|
411
|
+
await this.redis.quit();
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Set the current site ID (for multisite per-site cache isolation).
|
|
415
|
+
*/
|
|
416
|
+
setSiteId(siteId) {
|
|
417
|
+
this.siteId = siteId;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Register groups as global (shared across sites in multisite).
|
|
421
|
+
*/
|
|
422
|
+
addGlobalGroups(groups) {
|
|
423
|
+
for (const group of groups) {
|
|
424
|
+
this.globalGroups.add(group);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Set TTL for a specific cache group.
|
|
429
|
+
*/
|
|
430
|
+
setGroupTtl(group, ttlSeconds) {
|
|
431
|
+
this.groupTtl.set(group, ttlSeconds);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Build the Redis key for a cache entry.
|
|
435
|
+
*/
|
|
436
|
+
buildKey(key, group = "default") {
|
|
437
|
+
if (this.globalGroups.has(group)) {
|
|
438
|
+
return `${this.keyPrefix}:global:${group}:${key}`;
|
|
439
|
+
}
|
|
440
|
+
return `${this.keyPrefix}:site:${this.siteId}:${group}:${key}`;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Build a local cache key (in-memory, for current request).
|
|
444
|
+
*/
|
|
445
|
+
buildLocalKey(key, group = "default") {
|
|
446
|
+
if (this.globalGroups.has(group)) {
|
|
447
|
+
return `global:${group}:${key}`;
|
|
448
|
+
}
|
|
449
|
+
return `site:${this.siteId}:${group}:${key}`;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get TTL for a group in seconds.
|
|
453
|
+
*/
|
|
454
|
+
getTtl(group) {
|
|
455
|
+
return this.groupTtl.get(group) ?? this.defaultTtl;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get a cached value.
|
|
459
|
+
*
|
|
460
|
+
* @returns The cached value, or undefined if not found
|
|
461
|
+
*/
|
|
462
|
+
async get(key, group = "default") {
|
|
463
|
+
const localKey = this.buildLocalKey(key, group);
|
|
464
|
+
if (this.localCache.has(localKey)) {
|
|
465
|
+
return this.localCache.get(localKey);
|
|
466
|
+
}
|
|
467
|
+
const redisKey = this.buildKey(key, group);
|
|
468
|
+
const raw = await this.redis.get(redisKey);
|
|
469
|
+
if (raw === null) return void 0;
|
|
470
|
+
try {
|
|
471
|
+
const value = JSON.parse(raw);
|
|
472
|
+
this.localCache.set(localKey, value);
|
|
473
|
+
return value;
|
|
474
|
+
} catch {
|
|
475
|
+
return raw;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Set a cached value.
|
|
480
|
+
*
|
|
481
|
+
* @param key - Cache key
|
|
482
|
+
* @param value - Value to cache (will be JSON serialized)
|
|
483
|
+
* @param group - Cache group
|
|
484
|
+
* @param ttl - TTL in seconds (overrides group default). 0 = no expiry.
|
|
485
|
+
* @returns true on success
|
|
486
|
+
*/
|
|
487
|
+
async set(key, value, group = "default", ttl) {
|
|
488
|
+
const redisKey = this.buildKey(key, group);
|
|
489
|
+
const serialized = JSON.stringify(value);
|
|
490
|
+
const effectiveTtl = ttl ?? this.getTtl(group);
|
|
491
|
+
if (effectiveTtl > 0) {
|
|
492
|
+
await this.redis.setex(redisKey, effectiveTtl, serialized);
|
|
493
|
+
} else {
|
|
494
|
+
await this.redis.set(redisKey, serialized);
|
|
495
|
+
}
|
|
496
|
+
const localKey = this.buildLocalKey(key, group);
|
|
497
|
+
this.localCache.set(localKey, value);
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Add a cached value only if it doesn't already exist.
|
|
502
|
+
*
|
|
503
|
+
* @returns true if the value was added, false if key already exists
|
|
504
|
+
*/
|
|
505
|
+
async add(key, value, group = "default", ttl) {
|
|
506
|
+
const redisKey = this.buildKey(key, group);
|
|
507
|
+
const serialized = JSON.stringify(value);
|
|
508
|
+
const effectiveTtl = ttl ?? this.getTtl(group);
|
|
509
|
+
let result;
|
|
510
|
+
if (effectiveTtl > 0) {
|
|
511
|
+
result = await this.redis.set(redisKey, serialized, "EX", effectiveTtl, "NX");
|
|
512
|
+
} else {
|
|
513
|
+
result = await this.redis.set(redisKey, serialized, "NX");
|
|
514
|
+
}
|
|
515
|
+
if (result === "OK") {
|
|
516
|
+
const localKey = this.buildLocalKey(key, group);
|
|
517
|
+
this.localCache.set(localKey, value);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Delete a cached value.
|
|
524
|
+
*
|
|
525
|
+
* @returns true if the key existed and was deleted
|
|
526
|
+
*/
|
|
527
|
+
async delete(key, group = "default") {
|
|
528
|
+
const redisKey = this.buildKey(key, group);
|
|
529
|
+
const count = await this.redis.del(redisKey);
|
|
530
|
+
const localKey = this.buildLocalKey(key, group);
|
|
531
|
+
this.localCache.delete(localKey);
|
|
532
|
+
return count > 0;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Increment a numeric cached value.
|
|
536
|
+
*
|
|
537
|
+
* @returns The new value, or false if key doesn't exist
|
|
538
|
+
*/
|
|
539
|
+
async incr(key, group = "default", offset = 1) {
|
|
540
|
+
const redisKey = this.buildKey(key, group);
|
|
541
|
+
const keyExists = await this.redis.exists(redisKey);
|
|
542
|
+
if (!keyExists) return false;
|
|
543
|
+
const current = await this.get(key, group);
|
|
544
|
+
if (current === void 0 || typeof current !== "number") return false;
|
|
545
|
+
const newValue = current + offset;
|
|
546
|
+
await this.set(key, newValue, group);
|
|
547
|
+
return newValue;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Decrement a numeric cached value.
|
|
551
|
+
*
|
|
552
|
+
* @returns The new value, or false if key doesn't exist
|
|
553
|
+
*/
|
|
554
|
+
async decr(key, group = "default", offset = 1) {
|
|
555
|
+
return this.incr(key, group, -offset);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Get multiple cached values at once.
|
|
559
|
+
*
|
|
560
|
+
* @returns Map of key -> value (missing keys are not included)
|
|
561
|
+
*/
|
|
562
|
+
async getMultiple(keys, group = "default") {
|
|
563
|
+
const result = /* @__PURE__ */ new Map();
|
|
564
|
+
if (keys.length === 0) return result;
|
|
565
|
+
const redisKeys = keys.map((k) => this.buildKey(k, group));
|
|
566
|
+
const values = await this.redis.mget(...redisKeys);
|
|
567
|
+
for (let i = 0; i < keys.length; i++) {
|
|
568
|
+
const raw = values[i];
|
|
569
|
+
if (raw !== null) {
|
|
570
|
+
try {
|
|
571
|
+
const value = JSON.parse(raw);
|
|
572
|
+
result.set(keys[i], value);
|
|
573
|
+
const localKey = this.buildLocalKey(keys[i], group);
|
|
574
|
+
this.localCache.set(localKey, value);
|
|
575
|
+
} catch {
|
|
576
|
+
result.set(keys[i], raw);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Set multiple cached values at once.
|
|
584
|
+
*/
|
|
585
|
+
async setMultiple(entries, group = "default", ttl) {
|
|
586
|
+
const items = entries instanceof Map ? entries : new Map(Object.entries(entries));
|
|
587
|
+
if (items.size === 0) return true;
|
|
588
|
+
const effectiveTtl = ttl ?? this.getTtl(group);
|
|
589
|
+
const pipeline = this.redis.pipeline();
|
|
590
|
+
for (const [key, value] of items) {
|
|
591
|
+
const redisKey = this.buildKey(key, group);
|
|
592
|
+
const serialized = JSON.stringify(value);
|
|
593
|
+
if (effectiveTtl > 0) {
|
|
594
|
+
pipeline.setex(redisKey, effectiveTtl, serialized);
|
|
595
|
+
} else {
|
|
596
|
+
pipeline.set(redisKey, serialized);
|
|
597
|
+
}
|
|
598
|
+
const localKey = this.buildLocalKey(key, group);
|
|
599
|
+
this.localCache.set(localKey, value);
|
|
600
|
+
}
|
|
601
|
+
await pipeline.exec();
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Flush all keys in a specific group using SCAN + pipeline DEL.
|
|
606
|
+
*
|
|
607
|
+
* @returns Number of keys deleted
|
|
608
|
+
*/
|
|
609
|
+
async flushGroup(group) {
|
|
610
|
+
const pattern = this.globalGroups.has(group) ? `${this.keyPrefix}:global:${group}:*` : `${this.keyPrefix}:site:${this.siteId}:${group}:*`;
|
|
611
|
+
let deleted = 0;
|
|
612
|
+
let cursor = "0";
|
|
613
|
+
do {
|
|
614
|
+
const [nextCursor, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
615
|
+
cursor = nextCursor;
|
|
616
|
+
if (keys.length > 0) {
|
|
617
|
+
const pipeline = this.redis.pipeline();
|
|
618
|
+
for (const key of keys) {
|
|
619
|
+
pipeline.del(key);
|
|
620
|
+
}
|
|
621
|
+
await pipeline.exec();
|
|
622
|
+
deleted += keys.length;
|
|
623
|
+
}
|
|
624
|
+
} while (cursor !== "0");
|
|
625
|
+
for (const localKey of this.localCache.keys()) {
|
|
626
|
+
if (localKey.startsWith(`${group}:`)) {
|
|
627
|
+
this.localCache.delete(localKey);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return deleted;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Flush all cached data (all groups, all sites).
|
|
634
|
+
* Uses SCAN + DEL to only clear cache keys (not other Redis data).
|
|
635
|
+
*
|
|
636
|
+
* @returns Number of keys deleted
|
|
637
|
+
*/
|
|
638
|
+
async flushAll() {
|
|
639
|
+
const pattern = `${this.keyPrefix}:*`;
|
|
640
|
+
let deleted = 0;
|
|
641
|
+
let cursor = "0";
|
|
642
|
+
do {
|
|
643
|
+
const [nextCursor, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
644
|
+
cursor = nextCursor;
|
|
645
|
+
if (keys.length > 0) {
|
|
646
|
+
const pipeline = this.redis.pipeline();
|
|
647
|
+
for (const key of keys) {
|
|
648
|
+
pipeline.del(key);
|
|
649
|
+
}
|
|
650
|
+
await pipeline.exec();
|
|
651
|
+
deleted += keys.length;
|
|
652
|
+
}
|
|
653
|
+
} while (cursor !== "0");
|
|
654
|
+
this.localCache.clear();
|
|
655
|
+
return deleted;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Clear only the local in-memory cache (for request boundary cleanup).
|
|
659
|
+
*/
|
|
660
|
+
clearLocalCache() {
|
|
661
|
+
this.localCache.clear();
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Check if a key exists in cache.
|
|
665
|
+
*/
|
|
666
|
+
async exists(key, group = "default") {
|
|
667
|
+
const localKey = this.buildLocalKey(key, group);
|
|
668
|
+
if (this.localCache.has(localKey)) return true;
|
|
669
|
+
const redisKey = this.buildKey(key, group);
|
|
670
|
+
return await this.redis.exists(redisKey) === 1;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get the underlying Redis instance (for advanced operations).
|
|
674
|
+
*/
|
|
675
|
+
getRedis() {
|
|
676
|
+
return this.redis;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/repositories/options-repository.ts
|
|
681
|
+
import { eq } from "drizzle-orm";
|
|
682
|
+
var CACHE_GROUP = "options";
|
|
683
|
+
var AUTOLOAD_CACHE_KEY = "__autoloaded";
|
|
684
|
+
var NOT_FOUND_GROUP = "options_nf";
|
|
685
|
+
var OptionsRepository = class {
|
|
686
|
+
constructor(db, cache) {
|
|
687
|
+
this.db = db;
|
|
688
|
+
this.cache = cache;
|
|
689
|
+
}
|
|
690
|
+
db;
|
|
691
|
+
cache;
|
|
692
|
+
/**
|
|
693
|
+
* Get an option value.
|
|
694
|
+
*
|
|
695
|
+
* Lookup order:
|
|
696
|
+
* 1. Redis cache (group "options")
|
|
697
|
+
* 2. "Not found" cache (avoids repeated DB queries for missing keys)
|
|
698
|
+
* 3. Database
|
|
699
|
+
*/
|
|
700
|
+
async getOption(name, defaultValue) {
|
|
701
|
+
const cached = await this.cache.get(name, CACHE_GROUP);
|
|
702
|
+
if (cached !== void 0) return cached;
|
|
703
|
+
const isNotFound = await this.cache.get(name, NOT_FOUND_GROUP);
|
|
704
|
+
if (isNotFound !== void 0) return defaultValue;
|
|
705
|
+
const rows = await this.db.select().from(options).where(eq(options.optionName, name)).limit(1);
|
|
706
|
+
if (rows.length === 0) {
|
|
707
|
+
await this.cache.set(name, true, NOT_FOUND_GROUP, 3600);
|
|
708
|
+
return defaultValue;
|
|
709
|
+
}
|
|
710
|
+
const row = rows[0];
|
|
711
|
+
const value = this.deserializeValue(row);
|
|
712
|
+
await this.cache.set(name, value, CACHE_GROUP);
|
|
713
|
+
return value;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Add a new option. Fails if option already exists.
|
|
717
|
+
*
|
|
718
|
+
* @returns true if the option was created
|
|
719
|
+
*/
|
|
720
|
+
async addOption(name, value, autoload = true) {
|
|
721
|
+
const existing = await this.db.select({ optionId: options.optionId }).from(options).where(eq(options.optionName, name)).limit(1);
|
|
722
|
+
if (existing.length > 0) return false;
|
|
723
|
+
const { textValue, jsonValue } = this.serializeValue(value);
|
|
724
|
+
await this.db.insert(options).values({
|
|
725
|
+
optionName: name,
|
|
726
|
+
optionValue: textValue,
|
|
727
|
+
optionValueJson: jsonValue,
|
|
728
|
+
autoload
|
|
729
|
+
});
|
|
730
|
+
await this.cache.set(name, value, CACHE_GROUP);
|
|
731
|
+
await this.cache.delete(name, NOT_FOUND_GROUP);
|
|
732
|
+
if (autoload) {
|
|
733
|
+
await this.cache.delete(AUTOLOAD_CACHE_KEY, CACHE_GROUP);
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Update an existing option, or create it if it doesn't exist.
|
|
739
|
+
*
|
|
740
|
+
* @returns true if the value was changed
|
|
741
|
+
*/
|
|
742
|
+
async updateOption(name, value, autoload) {
|
|
743
|
+
const { textValue, jsonValue } = this.serializeValue(value);
|
|
744
|
+
const existing = await this.db.select().from(options).where(eq(options.optionName, name)).limit(1);
|
|
745
|
+
if (existing.length === 0) {
|
|
746
|
+
return this.addOption(name, value, autoload ?? true);
|
|
747
|
+
}
|
|
748
|
+
const row = existing[0];
|
|
749
|
+
if (row.optionValue === textValue && autoload === void 0) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
const updateData = {
|
|
753
|
+
optionValue: textValue,
|
|
754
|
+
optionValueJson: jsonValue
|
|
755
|
+
};
|
|
756
|
+
if (autoload !== void 0) {
|
|
757
|
+
updateData["autoload"] = autoload;
|
|
758
|
+
}
|
|
759
|
+
await this.db.update(options).set(updateData).where(eq(options.optionName, name));
|
|
760
|
+
await this.cache.set(name, value, CACHE_GROUP);
|
|
761
|
+
await this.cache.delete(name, NOT_FOUND_GROUP);
|
|
762
|
+
await this.cache.delete(AUTOLOAD_CACHE_KEY, CACHE_GROUP);
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Delete an option.
|
|
767
|
+
*
|
|
768
|
+
* @returns true if the option existed and was deleted
|
|
769
|
+
*/
|
|
770
|
+
async deleteOption(name) {
|
|
771
|
+
const result = await this.db.delete(options).where(eq(options.optionName, name)).returning({ optionId: options.optionId });
|
|
772
|
+
if (result.length === 0) return false;
|
|
773
|
+
await this.cache.delete(name, CACHE_GROUP);
|
|
774
|
+
await this.cache.delete(name, NOT_FOUND_GROUP);
|
|
775
|
+
await this.cache.delete(AUTOLOAD_CACHE_KEY, CACHE_GROUP);
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Load all autoloaded options into cache at once.
|
|
780
|
+
* Called during bootstrap to pre-warm the cache.
|
|
781
|
+
*/
|
|
782
|
+
async loadAutoloadedOptions() {
|
|
783
|
+
const cached = await this.cache.get(
|
|
784
|
+
AUTOLOAD_CACHE_KEY,
|
|
785
|
+
CACHE_GROUP
|
|
786
|
+
);
|
|
787
|
+
if (cached) {
|
|
788
|
+
const result2 = new Map(Object.entries(cached));
|
|
789
|
+
for (const [key, value] of result2) {
|
|
790
|
+
await this.cache.set(key, value, CACHE_GROUP);
|
|
791
|
+
}
|
|
792
|
+
return result2;
|
|
793
|
+
}
|
|
794
|
+
const rows = await this.db.select().from(options).where(eq(options.autoload, true));
|
|
795
|
+
const result = /* @__PURE__ */ new Map();
|
|
796
|
+
const autoloadMap = {};
|
|
797
|
+
for (const row of rows) {
|
|
798
|
+
const value = this.deserializeValue(row);
|
|
799
|
+
result.set(row.optionName, value);
|
|
800
|
+
autoloadMap[row.optionName] = value;
|
|
801
|
+
await this.cache.set(row.optionName, value, CACHE_GROUP);
|
|
802
|
+
}
|
|
803
|
+
await this.cache.set(AUTOLOAD_CACHE_KEY, autoloadMap, CACHE_GROUP);
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Serialize a value for storage.
|
|
808
|
+
* Complex types (objects, arrays) are stored in both text and JSONB columns.
|
|
809
|
+
*/
|
|
810
|
+
serializeValue(value) {
|
|
811
|
+
if (value === null || value === void 0) {
|
|
812
|
+
return { textValue: "", jsonValue: null };
|
|
813
|
+
}
|
|
814
|
+
if (typeof value === "string") {
|
|
815
|
+
try {
|
|
816
|
+
const parsed = JSON.parse(value);
|
|
817
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
818
|
+
return { textValue: value, jsonValue: parsed };
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
return { textValue: value, jsonValue: null };
|
|
823
|
+
}
|
|
824
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
825
|
+
return { textValue: String(value), jsonValue: null };
|
|
826
|
+
}
|
|
827
|
+
const textValue = JSON.stringify(value);
|
|
828
|
+
return { textValue, jsonValue: value };
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Deserialize a value from the database row.
|
|
832
|
+
* Prefers JSONB column when available (already parsed).
|
|
833
|
+
*/
|
|
834
|
+
deserializeValue(row) {
|
|
835
|
+
if (row.optionValueJson !== null && row.optionValueJson !== void 0) {
|
|
836
|
+
return row.optionValueJson;
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
return JSON.parse(row.optionValue);
|
|
840
|
+
} catch {
|
|
841
|
+
return row.optionValue;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// src/repositories/post-repository.ts
|
|
847
|
+
import { eq as eq3, and as and2, sql as sql2 } from "drizzle-orm";
|
|
848
|
+
|
|
849
|
+
// src/repositories/meta-repository.ts
|
|
850
|
+
import { eq as eq2, and, inArray, sql } from "drizzle-orm";
|
|
851
|
+
var MetaRepository = class {
|
|
852
|
+
constructor(db, table, columns, colNames) {
|
|
853
|
+
this.db = db;
|
|
854
|
+
this.table = table;
|
|
855
|
+
this.columns = columns;
|
|
856
|
+
this.colNames = colNames;
|
|
857
|
+
}
|
|
858
|
+
db;
|
|
859
|
+
table;
|
|
860
|
+
columns;
|
|
861
|
+
colNames;
|
|
862
|
+
async get(objectId, key) {
|
|
863
|
+
const rows = await this.db.select().from(this.table).where(and(eq2(this.columns.objectId, objectId), eq2(this.columns.metaKey, key))).limit(1);
|
|
864
|
+
if (rows.length === 0) return void 0;
|
|
865
|
+
return this.deserialize(this.toMetaEntry(rows[0]));
|
|
866
|
+
}
|
|
867
|
+
async getAll(objectId, key) {
|
|
868
|
+
const rows = await this.db.select().from(this.table).where(and(eq2(this.columns.objectId, objectId), eq2(this.columns.metaKey, key)));
|
|
869
|
+
return rows.map((r) => this.deserialize(this.toMetaEntry(r)));
|
|
870
|
+
}
|
|
871
|
+
async getAllForObject(objectId) {
|
|
872
|
+
const rows = await this.db.select().from(this.table).where(eq2(this.columns.objectId, objectId));
|
|
873
|
+
const result = /* @__PURE__ */ new Map();
|
|
874
|
+
for (const row of rows) {
|
|
875
|
+
const entry = this.toMetaEntry(row);
|
|
876
|
+
const key = entry.metaKey ?? "";
|
|
877
|
+
const values = result.get(key) ?? [];
|
|
878
|
+
values.push(this.deserialize(entry));
|
|
879
|
+
result.set(key, values);
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
async batchLoad(objectIds) {
|
|
884
|
+
if (objectIds.length === 0) return /* @__PURE__ */ new Map();
|
|
885
|
+
const rows = await this.db.select().from(this.table).where(inArray(this.columns.objectId, objectIds));
|
|
886
|
+
const result = /* @__PURE__ */ new Map();
|
|
887
|
+
for (const row of rows) {
|
|
888
|
+
const rawRow = row;
|
|
889
|
+
const entry = this.toMetaEntry(rawRow);
|
|
890
|
+
if (!result.has(entry.objectId)) {
|
|
891
|
+
result.set(entry.objectId, /* @__PURE__ */ new Map());
|
|
892
|
+
}
|
|
893
|
+
const objectMeta = result.get(entry.objectId);
|
|
894
|
+
const key = entry.metaKey ?? "";
|
|
895
|
+
const values = objectMeta.get(key) ?? [];
|
|
896
|
+
values.push(this.deserialize(entry));
|
|
897
|
+
objectMeta.set(key, values);
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
async add(objectId, key, value) {
|
|
902
|
+
const { textValue, jsonValue } = this.serialize(value);
|
|
903
|
+
const jsonStr = jsonValue !== null ? JSON.stringify(jsonValue) : null;
|
|
904
|
+
const s = this.colNames.sql;
|
|
905
|
+
const t = this.colNames.table;
|
|
906
|
+
const rows = await this.db.execute(sql`
|
|
907
|
+
INSERT INTO ${sql.raw(`"${t}"`)} (${sql.raw(`"${s.objectId}"`)}, ${sql.raw(`"${s.metaKey}"`)}, ${sql.raw(`"${s.metaValue}"`)}, ${sql.raw(`"${s.metaValueJson}"`)})
|
|
908
|
+
VALUES (${objectId}, ${key}, ${textValue}, ${jsonStr}::jsonb)
|
|
909
|
+
RETURNING ${sql.raw(`"${s.metaId}"`)}
|
|
910
|
+
`);
|
|
911
|
+
const returnedRows = rows;
|
|
912
|
+
return returnedRows[0][s.metaId];
|
|
913
|
+
}
|
|
914
|
+
async update(objectId, key, value) {
|
|
915
|
+
const { textValue, jsonValue } = this.serialize(value);
|
|
916
|
+
const existing = await this.db.select({ metaId: this.columns.metaId }).from(this.table).where(and(eq2(this.columns.objectId, objectId), eq2(this.columns.metaKey, key))).limit(1);
|
|
917
|
+
if (existing.length === 0) {
|
|
918
|
+
await this.add(objectId, key, value);
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
const metaId = existing[0].metaId;
|
|
922
|
+
const jsonStr = jsonValue !== null ? JSON.stringify(jsonValue) : null;
|
|
923
|
+
const s = this.colNames.sql;
|
|
924
|
+
const t = this.colNames.table;
|
|
925
|
+
await this.db.execute(sql`
|
|
926
|
+
UPDATE ${sql.raw(`"${t}"`)}
|
|
927
|
+
SET ${sql.raw(`"${s.metaValue}"`)} = ${textValue}, ${sql.raw(`"${s.metaValueJson}"`)} = ${jsonStr}::jsonb
|
|
928
|
+
WHERE ${sql.raw(`"${s.metaId}"`)} = ${metaId}
|
|
929
|
+
`);
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
async delete(objectId, key, value) {
|
|
933
|
+
if (value !== void 0) {
|
|
934
|
+
const { textValue } = this.serialize(value);
|
|
935
|
+
const result2 = await this.db.delete(this.table).where(
|
|
936
|
+
and(
|
|
937
|
+
eq2(this.columns.objectId, objectId),
|
|
938
|
+
eq2(this.columns.metaKey, key),
|
|
939
|
+
eq2(this.columns.metaValue, textValue)
|
|
940
|
+
)
|
|
941
|
+
).returning({ metaId: this.columns.metaId });
|
|
942
|
+
return result2.length;
|
|
943
|
+
}
|
|
944
|
+
const result = await this.db.delete(this.table).where(and(eq2(this.columns.objectId, objectId), eq2(this.columns.metaKey, key))).returning({ metaId: this.columns.metaId });
|
|
945
|
+
return result.length;
|
|
946
|
+
}
|
|
947
|
+
async deleteAllForObject(objectId) {
|
|
948
|
+
const result = await this.db.delete(this.table).where(eq2(this.columns.objectId, objectId)).returning({ metaId: this.columns.metaId });
|
|
949
|
+
return result.length;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Convert a raw DB row to MetaEntry using column name mapping.
|
|
953
|
+
*/
|
|
954
|
+
toMetaEntry(row) {
|
|
955
|
+
const ts = this.colNames.ts;
|
|
956
|
+
return {
|
|
957
|
+
metaId: row[ts.metaId],
|
|
958
|
+
objectId: row[ts.objectId],
|
|
959
|
+
metaKey: row[ts.metaKey],
|
|
960
|
+
metaValue: row[ts.metaValue],
|
|
961
|
+
metaValueJson: row[ts.metaValueJson]
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
serialize(value) {
|
|
965
|
+
if (value === null || value === void 0) {
|
|
966
|
+
return { textValue: "", jsonValue: null };
|
|
967
|
+
}
|
|
968
|
+
if (typeof value === "string") {
|
|
969
|
+
try {
|
|
970
|
+
const parsed = JSON.parse(value);
|
|
971
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
972
|
+
return { textValue: value, jsonValue: parsed };
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
return { textValue: value, jsonValue: null };
|
|
977
|
+
}
|
|
978
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
979
|
+
return { textValue: String(value), jsonValue: null };
|
|
980
|
+
}
|
|
981
|
+
const textValue = JSON.stringify(value);
|
|
982
|
+
return { textValue, jsonValue: value };
|
|
983
|
+
}
|
|
984
|
+
deserialize(entry) {
|
|
985
|
+
if (entry.metaValueJson !== null && entry.metaValueJson !== void 0) {
|
|
986
|
+
return entry.metaValueJson;
|
|
987
|
+
}
|
|
988
|
+
if (entry.metaValue === null) return "";
|
|
989
|
+
try {
|
|
990
|
+
return JSON.parse(entry.metaValue);
|
|
991
|
+
} catch {
|
|
992
|
+
return entry.metaValue;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/repositories/post-repository.ts
|
|
998
|
+
var PostRepository = class {
|
|
999
|
+
constructor(db) {
|
|
1000
|
+
this.db = db;
|
|
1001
|
+
const metaCols = {
|
|
1002
|
+
metaId: postmeta.metaId,
|
|
1003
|
+
objectId: postmeta.postId,
|
|
1004
|
+
metaKey: postmeta.metaKey,
|
|
1005
|
+
metaValue: postmeta.metaValue,
|
|
1006
|
+
metaValueJson: postmeta.metaValueJson
|
|
1007
|
+
};
|
|
1008
|
+
const colNames = {
|
|
1009
|
+
table: "postmeta",
|
|
1010
|
+
sql: { metaId: "meta_id", objectId: "post_id", metaKey: "meta_key", metaValue: "meta_value", metaValueJson: "meta_value_json" },
|
|
1011
|
+
ts: { metaId: "metaId", objectId: "postId", metaKey: "metaKey", metaValue: "metaValue", metaValueJson: "metaValueJson" }
|
|
1012
|
+
};
|
|
1013
|
+
this.meta = new MetaRepository(db, postmeta, metaCols, colNames);
|
|
1014
|
+
}
|
|
1015
|
+
db;
|
|
1016
|
+
meta;
|
|
1017
|
+
/**
|
|
1018
|
+
* Create a new post.
|
|
1019
|
+
*/
|
|
1020
|
+
async create(input) {
|
|
1021
|
+
const postName = input.postName || this.generateSlug(input.postTitle);
|
|
1022
|
+
const uniqueSlug = await this.ensureUniqueSlug(
|
|
1023
|
+
postName,
|
|
1024
|
+
input.postType ?? "post"
|
|
1025
|
+
);
|
|
1026
|
+
const now = /* @__PURE__ */ new Date();
|
|
1027
|
+
const [row] = await this.db.insert(posts).values({
|
|
1028
|
+
postAuthor: input.postAuthor,
|
|
1029
|
+
postTitle: input.postTitle,
|
|
1030
|
+
postContent: input.postContent ?? "",
|
|
1031
|
+
postExcerpt: input.postExcerpt ?? "",
|
|
1032
|
+
postStatus: input.postStatus ?? "draft",
|
|
1033
|
+
postName: uniqueSlug,
|
|
1034
|
+
postType: input.postType ?? "post",
|
|
1035
|
+
postParent: input.postParent ?? 0,
|
|
1036
|
+
postPassword: input.postPassword ?? "",
|
|
1037
|
+
commentStatus: input.commentStatus ?? "open",
|
|
1038
|
+
pingStatus: input.pingStatus ?? "open",
|
|
1039
|
+
postMimeType: input.postMimeType ?? "",
|
|
1040
|
+
menuOrder: input.menuOrder ?? 0,
|
|
1041
|
+
guid: input.guid ?? "",
|
|
1042
|
+
postDate: now,
|
|
1043
|
+
postDateGmt: now,
|
|
1044
|
+
postModified: now,
|
|
1045
|
+
postModifiedGmt: now
|
|
1046
|
+
}).returning();
|
|
1047
|
+
return row;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get a post by ID.
|
|
1051
|
+
*/
|
|
1052
|
+
async getById(id) {
|
|
1053
|
+
const rows = await this.db.select().from(posts).where(eq3(posts.id, id)).limit(1);
|
|
1054
|
+
return rows[0];
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Get a post by slug and type.
|
|
1058
|
+
*/
|
|
1059
|
+
async getBySlug(slug, postType = "post") {
|
|
1060
|
+
const rows = await this.db.select().from(posts).where(and2(eq3(posts.postName, slug), eq3(posts.postType, postType))).limit(1);
|
|
1061
|
+
return rows[0];
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Update a post. Returns the updated row or undefined if not found.
|
|
1065
|
+
*/
|
|
1066
|
+
async update(id, input) {
|
|
1067
|
+
const existing = await this.getById(id);
|
|
1068
|
+
if (!existing) return void 0;
|
|
1069
|
+
const now = /* @__PURE__ */ new Date();
|
|
1070
|
+
const updateData = {
|
|
1071
|
+
postModified: now,
|
|
1072
|
+
postModifiedGmt: now
|
|
1073
|
+
};
|
|
1074
|
+
if (input.postTitle !== void 0) updateData["postTitle"] = input.postTitle;
|
|
1075
|
+
if (input.postContent !== void 0) updateData["postContent"] = input.postContent;
|
|
1076
|
+
if (input.postExcerpt !== void 0) updateData["postExcerpt"] = input.postExcerpt;
|
|
1077
|
+
if (input.postStatus !== void 0) updateData["postStatus"] = input.postStatus;
|
|
1078
|
+
if (input.postType !== void 0) updateData["postType"] = input.postType;
|
|
1079
|
+
if (input.postParent !== void 0) updateData["postParent"] = input.postParent;
|
|
1080
|
+
if (input.postPassword !== void 0) updateData["postPassword"] = input.postPassword;
|
|
1081
|
+
if (input.commentStatus !== void 0) updateData["commentStatus"] = input.commentStatus;
|
|
1082
|
+
if (input.pingStatus !== void 0) updateData["pingStatus"] = input.pingStatus;
|
|
1083
|
+
if (input.menuOrder !== void 0) updateData["menuOrder"] = input.menuOrder;
|
|
1084
|
+
if (input.postName !== void 0) {
|
|
1085
|
+
updateData["postName"] = await this.ensureUniqueSlug(
|
|
1086
|
+
input.postName,
|
|
1087
|
+
input.postType ?? existing.postType,
|
|
1088
|
+
id
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
const [row] = await this.db.update(posts).set(updateData).where(eq3(posts.id, id)).returning();
|
|
1092
|
+
return row;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Trash a post (move to trash status, preserving original status in meta).
|
|
1096
|
+
*/
|
|
1097
|
+
async trash(id) {
|
|
1098
|
+
const existing = await this.getById(id);
|
|
1099
|
+
if (!existing) return void 0;
|
|
1100
|
+
if (existing.postStatus === "trash") return existing;
|
|
1101
|
+
await this.meta.update(id, "_trash_meta_status", existing.postStatus);
|
|
1102
|
+
return this.update(id, { postStatus: "trash" });
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Restore a trashed post to its original status.
|
|
1106
|
+
*/
|
|
1107
|
+
async untrash(id) {
|
|
1108
|
+
const existing = await this.getById(id);
|
|
1109
|
+
if (!existing || existing.postStatus !== "trash") return existing;
|
|
1110
|
+
const originalStatus = await this.meta.get(id, "_trash_meta_status");
|
|
1111
|
+
await this.meta.delete(id, "_trash_meta_status");
|
|
1112
|
+
return this.update(id, { postStatus: originalStatus ?? "draft" });
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Permanently delete a post and all its metadata.
|
|
1116
|
+
*/
|
|
1117
|
+
async deletePermanently(id) {
|
|
1118
|
+
await this.meta.deleteAllForObject(id);
|
|
1119
|
+
const result = await this.db.delete(posts).where(eq3(posts.id, id)).returning({ id: posts.id });
|
|
1120
|
+
return result.length > 0;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Get sticky post IDs.
|
|
1124
|
+
*/
|
|
1125
|
+
async getStickyIds() {
|
|
1126
|
+
const rows = await this.db.select({ postId: postmeta.postId }).from(postmeta).where(and2(eq3(postmeta.metaKey, "_sticky"), eq3(postmeta.metaValue, "1")));
|
|
1127
|
+
return rows.map((r) => r.postId);
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Set a post as sticky or not.
|
|
1131
|
+
*/
|
|
1132
|
+
async setSticky(id, sticky) {
|
|
1133
|
+
if (sticky) {
|
|
1134
|
+
await this.meta.update(id, "_sticky", "1");
|
|
1135
|
+
} else {
|
|
1136
|
+
await this.meta.delete(id, "_sticky");
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Check if a post is sticky.
|
|
1141
|
+
*/
|
|
1142
|
+
async isSticky(id) {
|
|
1143
|
+
const value = await this.meta.get(id, "_sticky");
|
|
1144
|
+
return value === "1" || value === 1;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Count posts by type and status.
|
|
1148
|
+
*/
|
|
1149
|
+
async countByStatus(postType = "post") {
|
|
1150
|
+
const rows = await this.db.select({
|
|
1151
|
+
status: posts.postStatus,
|
|
1152
|
+
count: sql2`count(*)::int`
|
|
1153
|
+
}).from(posts).where(eq3(posts.postType, postType)).groupBy(posts.postStatus);
|
|
1154
|
+
const result = {};
|
|
1155
|
+
for (const row of rows) {
|
|
1156
|
+
result[row.status] = row.count;
|
|
1157
|
+
}
|
|
1158
|
+
return result;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Generate a URL-safe slug from a title.
|
|
1162
|
+
*/
|
|
1163
|
+
generateSlug(title) {
|
|
1164
|
+
return title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 200);
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Ensure a slug is unique within a post type.
|
|
1168
|
+
* Appends -2, -3, etc. if needed.
|
|
1169
|
+
*/
|
|
1170
|
+
async ensureUniqueSlug(slug, postType, excludeId) {
|
|
1171
|
+
let candidate = slug;
|
|
1172
|
+
let suffix = 2;
|
|
1173
|
+
while (true) {
|
|
1174
|
+
const conditions = [eq3(posts.postName, candidate), eq3(posts.postType, postType)];
|
|
1175
|
+
if (excludeId !== void 0) {
|
|
1176
|
+
conditions.push(sql2`${posts.id} != ${excludeId}`);
|
|
1177
|
+
}
|
|
1178
|
+
const existing = await this.db.select({ id: posts.id }).from(posts).where(and2(...conditions)).limit(1);
|
|
1179
|
+
if (existing.length === 0) return candidate;
|
|
1180
|
+
candidate = `${slug}-${suffix}`;
|
|
1181
|
+
suffix++;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/repositories/taxonomy-repository.ts
|
|
1187
|
+
import { eq as eq4, and as and3, sql as sql3, inArray as inArray2 } from "drizzle-orm";
|
|
1188
|
+
var TaxonomyRepository = class {
|
|
1189
|
+
constructor(db) {
|
|
1190
|
+
this.db = db;
|
|
1191
|
+
const metaCols = {
|
|
1192
|
+
metaId: termmeta.metaId,
|
|
1193
|
+
objectId: termmeta.termId,
|
|
1194
|
+
metaKey: termmeta.metaKey,
|
|
1195
|
+
metaValue: termmeta.metaValue,
|
|
1196
|
+
metaValueJson: termmeta.metaValueJson
|
|
1197
|
+
};
|
|
1198
|
+
const colNames = {
|
|
1199
|
+
table: "termmeta",
|
|
1200
|
+
sql: { metaId: "meta_id", objectId: "term_id", metaKey: "meta_key", metaValue: "meta_value", metaValueJson: "meta_value_json" },
|
|
1201
|
+
ts: { metaId: "metaId", objectId: "termId", metaKey: "metaKey", metaValue: "metaValue", metaValueJson: "metaValueJson" }
|
|
1202
|
+
};
|
|
1203
|
+
this.meta = new MetaRepository(db, termmeta, metaCols, colNames);
|
|
1204
|
+
}
|
|
1205
|
+
db;
|
|
1206
|
+
meta;
|
|
1207
|
+
/**
|
|
1208
|
+
* Create a new term in a taxonomy.
|
|
1209
|
+
*/
|
|
1210
|
+
async createTerm(input) {
|
|
1211
|
+
const slug = input.slug || this.generateSlug(input.name);
|
|
1212
|
+
const uniqueSlug = await this.ensureUniqueSlug(slug);
|
|
1213
|
+
const [term] = await this.db.insert(terms).values({
|
|
1214
|
+
name: input.name,
|
|
1215
|
+
slug: uniqueSlug
|
|
1216
|
+
}).returning();
|
|
1217
|
+
const [tt] = await this.db.insert(termTaxonomy).values({
|
|
1218
|
+
termId: term.termId,
|
|
1219
|
+
taxonomy: input.taxonomy,
|
|
1220
|
+
description: input.description ?? "",
|
|
1221
|
+
parent: input.parent ?? 0,
|
|
1222
|
+
count: 0
|
|
1223
|
+
}).returning();
|
|
1224
|
+
return {
|
|
1225
|
+
termId: term.termId,
|
|
1226
|
+
name: term.name,
|
|
1227
|
+
slug: term.slug,
|
|
1228
|
+
termGroup: term.termGroup,
|
|
1229
|
+
termTaxonomyId: tt.termTaxonomyId,
|
|
1230
|
+
taxonomy: tt.taxonomy,
|
|
1231
|
+
description: tt.description,
|
|
1232
|
+
parent: tt.parent,
|
|
1233
|
+
count: tt.count
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Get a term by ID and taxonomy.
|
|
1238
|
+
*/
|
|
1239
|
+
async getTermById(termId, taxonomy) {
|
|
1240
|
+
const rows = await this.db.select({
|
|
1241
|
+
termId: terms.termId,
|
|
1242
|
+
name: terms.name,
|
|
1243
|
+
slug: terms.slug,
|
|
1244
|
+
termGroup: terms.termGroup,
|
|
1245
|
+
termTaxonomyId: termTaxonomy.termTaxonomyId,
|
|
1246
|
+
taxonomy: termTaxonomy.taxonomy,
|
|
1247
|
+
description: termTaxonomy.description,
|
|
1248
|
+
parent: termTaxonomy.parent,
|
|
1249
|
+
count: termTaxonomy.count
|
|
1250
|
+
}).from(terms).innerJoin(termTaxonomy, eq4(terms.termId, termTaxonomy.termId)).where(and3(eq4(terms.termId, termId), eq4(termTaxonomy.taxonomy, taxonomy))).limit(1);
|
|
1251
|
+
return rows[0];
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Get a term by slug and taxonomy.
|
|
1255
|
+
*/
|
|
1256
|
+
async getTermBySlug(slug, taxonomy) {
|
|
1257
|
+
const rows = await this.db.select({
|
|
1258
|
+
termId: terms.termId,
|
|
1259
|
+
name: terms.name,
|
|
1260
|
+
slug: terms.slug,
|
|
1261
|
+
termGroup: terms.termGroup,
|
|
1262
|
+
termTaxonomyId: termTaxonomy.termTaxonomyId,
|
|
1263
|
+
taxonomy: termTaxonomy.taxonomy,
|
|
1264
|
+
description: termTaxonomy.description,
|
|
1265
|
+
parent: termTaxonomy.parent,
|
|
1266
|
+
count: termTaxonomy.count
|
|
1267
|
+
}).from(terms).innerJoin(termTaxonomy, eq4(terms.termId, termTaxonomy.termId)).where(and3(eq4(terms.slug, slug), eq4(termTaxonomy.taxonomy, taxonomy))).limit(1);
|
|
1268
|
+
return rows[0];
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Get all terms in a taxonomy.
|
|
1272
|
+
*/
|
|
1273
|
+
async getTerms(taxonomy, parentId) {
|
|
1274
|
+
const conditions = [eq4(termTaxonomy.taxonomy, taxonomy)];
|
|
1275
|
+
if (parentId !== void 0) {
|
|
1276
|
+
conditions.push(eq4(termTaxonomy.parent, parentId));
|
|
1277
|
+
}
|
|
1278
|
+
const rows = await this.db.select({
|
|
1279
|
+
termId: terms.termId,
|
|
1280
|
+
name: terms.name,
|
|
1281
|
+
slug: terms.slug,
|
|
1282
|
+
termGroup: terms.termGroup,
|
|
1283
|
+
termTaxonomyId: termTaxonomy.termTaxonomyId,
|
|
1284
|
+
taxonomy: termTaxonomy.taxonomy,
|
|
1285
|
+
description: termTaxonomy.description,
|
|
1286
|
+
parent: termTaxonomy.parent,
|
|
1287
|
+
count: termTaxonomy.count
|
|
1288
|
+
}).from(terms).innerJoin(termTaxonomy, eq4(terms.termId, termTaxonomy.termId)).where(and3(...conditions)).orderBy(terms.name);
|
|
1289
|
+
return rows;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Assign terms to an object (post).
|
|
1293
|
+
*/
|
|
1294
|
+
async setObjectTerms(objectId, termTaxonomyIds) {
|
|
1295
|
+
const existingTtIds = termTaxonomyIds.length > 0 ? await this.db.select({ taxonomy: termTaxonomy.taxonomy }).from(termTaxonomy).where(inArray2(termTaxonomy.termTaxonomyId, termTaxonomyIds)) : [];
|
|
1296
|
+
const taxonomies = [...new Set(existingTtIds.map((r) => r.taxonomy))];
|
|
1297
|
+
if (taxonomies.length > 0) {
|
|
1298
|
+
const allTtIds = await this.db.select({ termTaxonomyId: termTaxonomy.termTaxonomyId }).from(termTaxonomy).where(inArray2(termTaxonomy.taxonomy, taxonomies));
|
|
1299
|
+
const allTtIdValues = allTtIds.map((r) => r.termTaxonomyId);
|
|
1300
|
+
if (allTtIdValues.length > 0) {
|
|
1301
|
+
await this.db.delete(termRelationships).where(
|
|
1302
|
+
and3(
|
|
1303
|
+
eq4(termRelationships.objectId, objectId),
|
|
1304
|
+
inArray2(termRelationships.termTaxonomyId, allTtIdValues)
|
|
1305
|
+
)
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (termTaxonomyIds.length > 0) {
|
|
1310
|
+
await this.db.insert(termRelationships).values(
|
|
1311
|
+
termTaxonomyIds.map((ttId, i) => ({
|
|
1312
|
+
objectId,
|
|
1313
|
+
termTaxonomyId: ttId,
|
|
1314
|
+
termOrder: i
|
|
1315
|
+
}))
|
|
1316
|
+
).onConflictDoNothing();
|
|
1317
|
+
}
|
|
1318
|
+
await this.recountTerms(termTaxonomyIds);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Get terms assigned to an object.
|
|
1322
|
+
*/
|
|
1323
|
+
async getObjectTerms(objectId, taxonomy) {
|
|
1324
|
+
const conditions = [eq4(termRelationships.objectId, objectId)];
|
|
1325
|
+
if (taxonomy) {
|
|
1326
|
+
conditions.push(eq4(termTaxonomy.taxonomy, taxonomy));
|
|
1327
|
+
}
|
|
1328
|
+
const rows = await this.db.select({
|
|
1329
|
+
termId: terms.termId,
|
|
1330
|
+
name: terms.name,
|
|
1331
|
+
slug: terms.slug,
|
|
1332
|
+
termGroup: terms.termGroup,
|
|
1333
|
+
termTaxonomyId: termTaxonomy.termTaxonomyId,
|
|
1334
|
+
taxonomy: termTaxonomy.taxonomy,
|
|
1335
|
+
description: termTaxonomy.description,
|
|
1336
|
+
parent: termTaxonomy.parent,
|
|
1337
|
+
count: termTaxonomy.count
|
|
1338
|
+
}).from(termRelationships).innerJoin(
|
|
1339
|
+
termTaxonomy,
|
|
1340
|
+
eq4(termRelationships.termTaxonomyId, termTaxonomy.termTaxonomyId)
|
|
1341
|
+
).innerJoin(terms, eq4(termTaxonomy.termId, terms.termId)).where(and3(...conditions)).orderBy(termRelationships.termOrder);
|
|
1342
|
+
return rows;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Delete a term and all its relationships.
|
|
1346
|
+
*/
|
|
1347
|
+
async deleteTerm(termId, taxonomy) {
|
|
1348
|
+
const term = await this.getTermById(termId, taxonomy);
|
|
1349
|
+
if (!term) return false;
|
|
1350
|
+
await this.db.delete(termRelationships).where(eq4(termRelationships.termTaxonomyId, term.termTaxonomyId));
|
|
1351
|
+
await this.db.delete(termTaxonomy).where(eq4(termTaxonomy.termTaxonomyId, term.termTaxonomyId));
|
|
1352
|
+
const otherUsages = await this.db.select({ termTaxonomyId: termTaxonomy.termTaxonomyId }).from(termTaxonomy).where(eq4(termTaxonomy.termId, termId)).limit(1);
|
|
1353
|
+
if (otherUsages.length === 0) {
|
|
1354
|
+
await this.db.delete(terms).where(eq4(terms.termId, termId));
|
|
1355
|
+
}
|
|
1356
|
+
await this.meta.deleteAllForObject(termId);
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Recount the number of objects assigned to terms.
|
|
1361
|
+
*/
|
|
1362
|
+
async recountTerms(termTaxonomyIds) {
|
|
1363
|
+
for (const ttId of termTaxonomyIds) {
|
|
1364
|
+
const [result] = await this.db.select({ count: sql3`count(*)::int` }).from(termRelationships).where(eq4(termRelationships.termTaxonomyId, ttId));
|
|
1365
|
+
await this.db.update(termTaxonomy).set({ count: result.count }).where(eq4(termTaxonomy.termTaxonomyId, ttId));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
generateSlug(name) {
|
|
1369
|
+
return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 200);
|
|
1370
|
+
}
|
|
1371
|
+
async ensureUniqueSlug(slug, excludeId) {
|
|
1372
|
+
let candidate = slug;
|
|
1373
|
+
let suffix = 2;
|
|
1374
|
+
while (true) {
|
|
1375
|
+
const conditions = [eq4(terms.slug, candidate)];
|
|
1376
|
+
if (excludeId !== void 0) {
|
|
1377
|
+
conditions.push(sql3`${terms.termId} != ${excludeId}`);
|
|
1378
|
+
}
|
|
1379
|
+
const existing = await this.db.select({ termId: terms.termId }).from(terms).where(and3(...conditions)).limit(1);
|
|
1380
|
+
if (existing.length === 0) return candidate;
|
|
1381
|
+
candidate = `${slug}-${suffix}`;
|
|
1382
|
+
suffix++;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
// src/repositories/revision-repository.ts
|
|
1388
|
+
import { eq as eq5, and as and4, desc, sql as sql4 } from "drizzle-orm";
|
|
1389
|
+
var REVISION_FIELDS = [
|
|
1390
|
+
"postTitle",
|
|
1391
|
+
"postContent",
|
|
1392
|
+
"postExcerpt"
|
|
1393
|
+
];
|
|
1394
|
+
var RevisionRepository = class {
|
|
1395
|
+
constructor(db) {
|
|
1396
|
+
this.db = db;
|
|
1397
|
+
}
|
|
1398
|
+
db;
|
|
1399
|
+
/**
|
|
1400
|
+
* Create a revision snapshot of a post.
|
|
1401
|
+
* Returns undefined if nothing changed since the last revision.
|
|
1402
|
+
*
|
|
1403
|
+
* @param post - The current state of the post (before or after update)
|
|
1404
|
+
* @param authorId - The user who made the change
|
|
1405
|
+
*/
|
|
1406
|
+
async createRevision(post, authorId) {
|
|
1407
|
+
const lastRevision = await this.getLatest(post.id);
|
|
1408
|
+
if (lastRevision && !this.hasChanges(post, lastRevision)) {
|
|
1409
|
+
return void 0;
|
|
1410
|
+
}
|
|
1411
|
+
const now = /* @__PURE__ */ new Date();
|
|
1412
|
+
const [revision] = await this.db.insert(posts).values({
|
|
1413
|
+
postAuthor: authorId,
|
|
1414
|
+
postDate: now,
|
|
1415
|
+
postDateGmt: now,
|
|
1416
|
+
postModified: now,
|
|
1417
|
+
postModifiedGmt: now,
|
|
1418
|
+
postTitle: post.postTitle,
|
|
1419
|
+
postContent: post.postContent,
|
|
1420
|
+
postExcerpt: post.postExcerpt,
|
|
1421
|
+
postStatus: "inherit",
|
|
1422
|
+
postName: `${post.id}-revision-v1`,
|
|
1423
|
+
postType: "revision",
|
|
1424
|
+
postParent: post.id
|
|
1425
|
+
}).returning();
|
|
1426
|
+
return revision;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Get all revisions for a post, newest first.
|
|
1430
|
+
*/
|
|
1431
|
+
async getRevisions(postId, limit) {
|
|
1432
|
+
let query = this.db.select().from(posts).where(and4(eq5(posts.postParent, postId), eq5(posts.postType, "revision"))).orderBy(desc(posts.postDate)).$dynamic();
|
|
1433
|
+
if (limit !== void 0) {
|
|
1434
|
+
query = query.limit(limit);
|
|
1435
|
+
}
|
|
1436
|
+
return await query;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Get the most recent revision for a post.
|
|
1440
|
+
*/
|
|
1441
|
+
async getLatest(postId) {
|
|
1442
|
+
const rows = await this.getRevisions(postId, 1);
|
|
1443
|
+
return rows[0];
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Get a specific revision by ID.
|
|
1447
|
+
*/
|
|
1448
|
+
async getById(revisionId) {
|
|
1449
|
+
const rows = await this.db.select().from(posts).where(and4(eq5(posts.id, revisionId), eq5(posts.postType, "revision"))).limit(1);
|
|
1450
|
+
return rows[0];
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Restore a post to a specific revision.
|
|
1454
|
+
* Returns the updated post.
|
|
1455
|
+
*/
|
|
1456
|
+
async restore(postId, revisionId) {
|
|
1457
|
+
const revision = await this.getById(revisionId);
|
|
1458
|
+
if (!revision || revision.postParent !== postId) return void 0;
|
|
1459
|
+
const now = /* @__PURE__ */ new Date();
|
|
1460
|
+
const [updated] = await this.db.update(posts).set({
|
|
1461
|
+
postTitle: revision.postTitle,
|
|
1462
|
+
postContent: revision.postContent,
|
|
1463
|
+
postExcerpt: revision.postExcerpt,
|
|
1464
|
+
postModified: now,
|
|
1465
|
+
postModifiedGmt: now
|
|
1466
|
+
}).where(eq5(posts.id, postId)).returning();
|
|
1467
|
+
return updated;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Delete old revisions beyond the keep limit.
|
|
1471
|
+
*/
|
|
1472
|
+
async cleanup(postId, keepCount) {
|
|
1473
|
+
const revisions = await this.getRevisions(postId);
|
|
1474
|
+
if (revisions.length <= keepCount) return 0;
|
|
1475
|
+
const toDelete = revisions.slice(keepCount);
|
|
1476
|
+
const idsToDelete = toDelete.map((r) => r.id);
|
|
1477
|
+
if (idsToDelete.length === 0) return 0;
|
|
1478
|
+
const result = await this.db.delete(posts).where(
|
|
1479
|
+
and4(
|
|
1480
|
+
sql4`${posts.id} IN (${sql4.join(
|
|
1481
|
+
idsToDelete.map((id) => sql4`${id}`),
|
|
1482
|
+
sql4`, `
|
|
1483
|
+
)})`,
|
|
1484
|
+
eq5(posts.postType, "revision")
|
|
1485
|
+
)
|
|
1486
|
+
).returning({ id: posts.id });
|
|
1487
|
+
return result.length;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Count revisions for a post.
|
|
1491
|
+
*/
|
|
1492
|
+
async count(postId) {
|
|
1493
|
+
const [result] = await this.db.select({ count: sql4`count(*)::int` }).from(posts).where(and4(eq5(posts.postParent, postId), eq5(posts.postType, "revision")));
|
|
1494
|
+
return result.count;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Check if any tracked field changed between two post states.
|
|
1498
|
+
*/
|
|
1499
|
+
hasChanges(current, previous) {
|
|
1500
|
+
for (const field of REVISION_FIELDS) {
|
|
1501
|
+
if (current[field] !== previous[field]) return true;
|
|
1502
|
+
}
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
export {
|
|
1507
|
+
MetaRepository,
|
|
1508
|
+
ObjectCache,
|
|
1509
|
+
OptionsRepository,
|
|
1510
|
+
PostRepository,
|
|
1511
|
+
RevisionRepository,
|
|
1512
|
+
TaxonomyRepository,
|
|
1513
|
+
commentmeta,
|
|
1514
|
+
comments,
|
|
1515
|
+
createConnection,
|
|
1516
|
+
links,
|
|
1517
|
+
options,
|
|
1518
|
+
postmeta,
|
|
1519
|
+
posts,
|
|
1520
|
+
scheduledEvents,
|
|
1521
|
+
sessions,
|
|
1522
|
+
termRelationships,
|
|
1523
|
+
termTaxonomy,
|
|
1524
|
+
termmeta,
|
|
1525
|
+
terms,
|
|
1526
|
+
usermeta,
|
|
1527
|
+
users
|
|
1528
|
+
};
|
|
9
1529
|
//# sourceMappingURL=index.js.map
|