@velora-cms/server 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/admin/sync-admin-bundle.d.ts +9 -0
- package/dist/admin/sync-admin-bundle.js +120 -0
- package/dist/admin-bundle/.velora-version +1 -0
- package/dist/admin-bundle/assets/{_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js → _virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DlIgIuFP.js} +1 -1
- package/dist/admin-bundle/assets/{_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js → _virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-Bm9EahCG.js} +2 -2
- package/dist/admin-bundle/assets/{content-trash.schema-DfXHkC-w.js → content-trash.schema-tvJ4Lbos.js} +5 -5
- package/dist/admin-bundle/assets/{hostInit--C4xK6nj.js → hostInit-B-6-34XY.js} +1 -1
- package/dist/admin-bundle/assets/{index-DhMY-0sC.js → index-CHCGuSbb.js} +1 -1
- package/dist/admin-bundle/assets/{index-BIAF0ShP.js → index-DYO7-IK8.js} +33 -33
- package/dist/admin-bundle/assets/{mf-entry-bootstrap-0-aef015cd.js → mf-entry-bootstrap-0-7b0ce2fe.js} +2 -2
- package/dist/admin-bundle/assets/{remoteEntry-CNyN68bc.js → remoteEntry-J2yJXsGB.js} +2 -2
- package/dist/admin-bundle/index.html +4 -4
- package/dist/app.d.ts +16 -1
- package/dist/app.js +63 -4
- package/dist/auth/api-keys.d.ts +1 -0
- package/dist/auth/api-keys.js +5 -1
- package/dist/auth/route-rate-limits.d.ts +4 -0
- package/dist/auth/route-rate-limits.js +13 -0
- package/dist/bin-sync-admin.d.ts +2 -0
- package/dist/bin-sync-admin.js +39 -0
- package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
- package/dist/config/public-url.d.ts +3 -0
- package/dist/config/public-url.js +45 -0
- package/dist/db/core-migrations/steps.js +95 -16
- package/dist/docs-bundle/pagefind/fragment/en_42f5356.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_6c5ee82.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_80941b8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_a64440b.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_eb168aa.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_f053753.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_33ea61e.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_bb7c383.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ff6eddf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -1
- package/dist/docs-bundle/pagefind/pagefind.en_6bbff64ea9.pf_meta +0 -0
- package/dist/docs-bundle/v1/index.html +4 -4
- package/dist/docs-bundle/v1/using-velora/getting-started/index.html +5 -3
- package/dist/docs-bundle/v1/using-velora/headless-api/index.html +10 -4
- package/dist/docs-bundle/v1/using-velora/index.html +6 -4
- package/dist/docs-bundle/v1/using-velora/installation/index.html +73 -15
- package/dist/docs-bundle/v1/using-velora/media/index.html +118 -34
- package/dist/graphql/schema.d.ts +1 -0
- package/dist/graphql/schema.js +5 -2
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +45 -11
- package/dist/media/media-service.d.ts +14 -15
- package/dist/media/media-service.js +121 -59
- package/dist/media/media-tokens.d.ts +6 -0
- package/dist/media/media-tokens.js +88 -0
- package/dist/media/storage/local.d.ts +16 -0
- package/dist/media/storage/local.js +99 -0
- package/dist/media/storage/s3.d.ts +25 -0
- package/dist/media/storage/s3.js +134 -0
- package/dist/media/storage/types.d.ts +21 -0
- package/dist/media/storage/types.js +44 -0
- package/dist/routes/admin-static.d.ts +2 -0
- package/dist/routes/admin-static.js +42 -4
- package/dist/routes/media-public.d.ts +6 -0
- package/dist/routes/media-public.js +265 -0
- package/dist/routes/media.d.ts +2 -1
- package/dist/routes/media.js +95 -5
- package/dist/routes/plugin-storage.js +5 -1
- package/dist/routes/public-api.js +5 -3
- package/dist/routes/published-item.d.ts +2 -1
- package/dist/routes/published-item.js +28 -5
- package/dist/scripts/copy-admin-bundle.js +9 -1
- package/package.json +12 -11
- package/prisma/schema.prisma +16 -3
- package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
package/dist/routes/media.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ApiErrorSchema, MediaDeleteResponseSchema, MediaFileResponseSchema, MediaListQuerySchema, MediaListResponseSchema, } from "@velora-cms/api-schemas";
|
|
2
|
+
import { ApiErrorSchema, MediaDeleteResponseSchema, MediaFileResponseSchema, MediaLinkResponseSchema, MediaListQuerySchema, MediaListResponseSchema, UpdateMediaVisibilityRequestSchema, } from "@velora-cms/api-schemas";
|
|
3
3
|
import { requireAuth } from "../auth/require-auth.js";
|
|
4
|
+
import { sanitizeForKey } from "../media/media-service.js";
|
|
5
|
+
import { mintMediaToken } from "../media/media-tokens.js";
|
|
4
6
|
const MediaParamsSchema = z.object({ id: z.string().uuid() });
|
|
7
|
+
const PATH_SEPARATOR_RE = /[\\/]/;
|
|
8
|
+
// LOW-11: multipart file parts can't be described by the Zod `schema`
|
|
9
|
+
// option (Fastify/Zod validate JSON bodies, not multipart streams), so
|
|
10
|
+
// this runs by hand right after `request.file()` resolves, before the
|
|
11
|
+
// filename is ever handed to MediaService.upload (which turns it
|
|
12
|
+
// straight into a storage key via sanitizeForKey). Defense in depth
|
|
13
|
+
// alongside HIGH-2's driver-level assertValidStorageKey: this rejects
|
|
14
|
+
// with a friendly, rule-naming 400 instead of a generic 500 from
|
|
15
|
+
// whatever the driver does with a hostile key.
|
|
16
|
+
//
|
|
17
|
+
// In practice @fastify/multipart's busboy already runs its own
|
|
18
|
+
// basename()-style stripping on `filename` before this ever sees it
|
|
19
|
+
// (path components dropped, a bare "." or ".." collapsed to "" and
|
|
20
|
+
// caught by the pre-existing `!file.filename` check) — see
|
|
21
|
+
// media.test.ts for direct unit coverage of every branch below, since
|
|
22
|
+
// an HTTP round-trip can no longer exercise the path-separator branch
|
|
23
|
+
// with the multipart plugin's default (non-preservePath) config. This
|
|
24
|
+
// still guards the cases busboy does NOT touch (e.g. a filename that
|
|
25
|
+
// sanitizes to nothing without being "."/".." verbatim) and stays
|
|
26
|
+
// correct if that config or an internal caller ever changes.
|
|
27
|
+
export function invalidUploadFilenameReason(filename) {
|
|
28
|
+
if (filename === "." || filename === "..") {
|
|
29
|
+
return 'Filename must not be "." or ".."';
|
|
30
|
+
}
|
|
31
|
+
if (PATH_SEPARATOR_RE.test(filename)) {
|
|
32
|
+
return "Filename must not contain path separators (\"/\" or \"\\\")";
|
|
33
|
+
}
|
|
34
|
+
if (sanitizeForKey(filename).length === 0) {
|
|
35
|
+
return "Filename must not be empty after sanitization";
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
5
39
|
export const mediaRoutes = async (app, opts) => {
|
|
6
40
|
const { mediaService } = opts;
|
|
7
41
|
async function toResponse(media) {
|
|
@@ -25,6 +59,10 @@ export const mediaRoutes = async (app, opts) => {
|
|
|
25
59
|
if (!file || !file.filename) {
|
|
26
60
|
return reply.code(400).send({ message: "A multipart file field is required" });
|
|
27
61
|
}
|
|
62
|
+
const filenameError = invalidUploadFilenameReason(file.filename);
|
|
63
|
+
if (filenameError) {
|
|
64
|
+
return reply.code(400).send({ message: filenameError });
|
|
65
|
+
}
|
|
28
66
|
const data = await file.toBuffer();
|
|
29
67
|
const media = await mediaService.upload({
|
|
30
68
|
filename: file.filename,
|
|
@@ -35,10 +73,10 @@ export const mediaRoutes = async (app, opts) => {
|
|
|
35
73
|
return reply.code(201).send(await toResponse(media));
|
|
36
74
|
});
|
|
37
75
|
// Media library listing — admin-authed (SEC-6, S175 audit). This lists
|
|
38
|
-
// EVERY uploaded asset regardless of publish state,
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
76
|
+
// EVERY uploaded asset regardless of publish state, so it must not be
|
|
77
|
+
// anonymously enumerable; published/headless media has its own
|
|
78
|
+
// API-key-gated /api/v1/media surface. The admin UI reaches this
|
|
79
|
+
// through authFetch.
|
|
42
80
|
app.get("/api/media", {
|
|
43
81
|
onRequest: requireAuth,
|
|
44
82
|
schema: {
|
|
@@ -72,6 +110,58 @@ export const mediaRoutes = async (app, opts) => {
|
|
|
72
110
|
}
|
|
73
111
|
return toResponse(media);
|
|
74
112
|
});
|
|
113
|
+
// Flips the visibility flag (S186c §2) — admin-authed, same as every
|
|
114
|
+
// other /api/media route (this is the backoffice library surface, not
|
|
115
|
+
// the API-key-gated /api/v1/media headless read path). No file move, no
|
|
116
|
+
// key/URL change; only the row changes.
|
|
117
|
+
app.patch("/api/media/:id", {
|
|
118
|
+
onRequest: requireAuth,
|
|
119
|
+
schema: {
|
|
120
|
+
params: MediaParamsSchema,
|
|
121
|
+
body: UpdateMediaVisibilityRequestSchema,
|
|
122
|
+
response: {
|
|
123
|
+
200: MediaFileResponseSchema,
|
|
124
|
+
400: ApiErrorSchema,
|
|
125
|
+
401: ApiErrorSchema,
|
|
126
|
+
404: ApiErrorSchema,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}, async (request, reply) => {
|
|
130
|
+
const media = await mediaService.getById(request.params.id);
|
|
131
|
+
if (!media) {
|
|
132
|
+
return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
|
|
133
|
+
}
|
|
134
|
+
const updated = await mediaService.setVisibility(request.params.id, request.body.visibility);
|
|
135
|
+
return toResponse(updated);
|
|
136
|
+
});
|
|
137
|
+
// Mints a signed, short-lived link to a media item (S186c §3) — the
|
|
138
|
+
// "Copy temporary link" button behind a private item in the admin.
|
|
139
|
+
// Same requireAuth idiom as every other /api/media route (any
|
|
140
|
+
// signed-in admin user, not a role gate) — mandate 4 of the S186c
|
|
141
|
+
// Task 3 review round: this is the file's own admin-session auth, kept
|
|
142
|
+
// consistent across the whole /api/media surface rather than inventing
|
|
143
|
+
// a stricter check just for this route. Works for a public item too
|
|
144
|
+
// (harmless — the plain stable path already serves it with no token
|
|
145
|
+
// needed); the admin UI simply only surfaces the button for private
|
|
146
|
+
// ones.
|
|
147
|
+
app.post("/api/media/:id/link", {
|
|
148
|
+
onRequest: requireAuth,
|
|
149
|
+
schema: {
|
|
150
|
+
params: MediaParamsSchema,
|
|
151
|
+
response: {
|
|
152
|
+
200: MediaLinkResponseSchema,
|
|
153
|
+
401: ApiErrorSchema,
|
|
154
|
+
404: ApiErrorSchema,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}, async (request, reply) => {
|
|
158
|
+
const media = await mediaService.getById(request.params.id);
|
|
159
|
+
if (!media) {
|
|
160
|
+
return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
|
|
161
|
+
}
|
|
162
|
+
const { token, expiresAt } = mintMediaToken(media.id);
|
|
163
|
+
return { url: `${media.url}?token=${encodeURIComponent(token)}`, expiresAt };
|
|
164
|
+
});
|
|
75
165
|
app.delete("/api/media/:id", {
|
|
76
166
|
onRequest: requireAuth,
|
|
77
167
|
schema: {
|
|
@@ -22,7 +22,11 @@ import { PluginNotActiveError, PluginNotInstalledError } from "../plugins/plugin
|
|
|
22
22
|
// - This was NOT the CMS's only unauthenticated read of internal content
|
|
23
23
|
// data — GET /api/media and GET /api/media/:id
|
|
24
24
|
// (apps/server/src/routes/media.ts:65-66, 84-85) are unauthenticated
|
|
25
|
-
// and return media items each carrying a
|
|
25
|
+
// and return media items each carrying a downloadUrl (M3 fix wave: no
|
|
26
|
+
// longer presigned/time-limited as of S186c — a stable app path,
|
|
27
|
+
// `/media/<id>/<filename>`, that GET /media/:id/:filename streams
|
|
28
|
+
// server-side; see media-service.ts's getDownloadUrl and
|
|
29
|
+
// apps/server/prisma/schema.prisma's storageKey field comment),
|
|
26
30
|
// and GET /api/document-types (apps/server/src/routes/document-types.ts:146-147),
|
|
27
31
|
// GET /api/templates (apps/server/src/routes/templates.ts:40-41),
|
|
28
32
|
// GET /api/themes (apps/server/src/routes/themes.ts:15-16), and
|
|
@@ -11,7 +11,7 @@ import { isPublicDocsEnabled } from "../config/public-docs.js";
|
|
|
11
11
|
import { maxQueryComplexity, maxQueryDepth } from "../config/graphql-limits.js";
|
|
12
12
|
import { createFieldCountRule } from "../graphql/field-count-rule.js";
|
|
13
13
|
import { buildGraphqlSchema } from "../graphql/schema.js";
|
|
14
|
-
import { computeExtensions, isNotFoundError, preparePublishedItem, resolveItemNode, resolveSubtreeFilter, toPublicDocumentType, toPublicMediaFile, toSummaries, } from "./published-item.js";
|
|
14
|
+
import { computeExtensions, isNotFoundError, preparePublishedItem, resolveItemNode, resolvePublicBaseUrl, resolveSubtreeFilter, toPublicDocumentType, toPublicMediaFile, toSummaries, } from "./published-item.js";
|
|
15
15
|
const PublicContentParamsSchema = z.object({ id: z.string().uuid() });
|
|
16
16
|
// mercurius' own /api/v1/graphql routes carry plain JSON-Schema objects for
|
|
17
17
|
// their request/response envelope (not Zod). This app's validatorCompiler/
|
|
@@ -400,7 +400,8 @@ export const publicApiRoutes = async (app, opts) => {
|
|
|
400
400
|
}, async (request) => {
|
|
401
401
|
const { cursor, limit, search } = request.query;
|
|
402
402
|
const files = await mediaService.list({ cursor, limit, search });
|
|
403
|
-
const
|
|
403
|
+
const baseUrl = resolvePublicBaseUrl(request);
|
|
404
|
+
const items = await Promise.all(files.map((file) => toPublicMediaFile(mediaService, file, baseUrl)));
|
|
404
405
|
const nextCursor = files.length === limit ? (files[files.length - 1]?.id ?? null) : null;
|
|
405
406
|
return { items, nextCursor };
|
|
406
407
|
});
|
|
@@ -415,7 +416,7 @@ export const publicApiRoutes = async (app, opts) => {
|
|
|
415
416
|
if (!media) {
|
|
416
417
|
return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
|
|
417
418
|
}
|
|
418
|
-
return toPublicMediaFile(mediaService, media);
|
|
419
|
+
return toPublicMediaFile(mediaService, media, resolvePublicBaseUrl(request));
|
|
419
420
|
});
|
|
420
421
|
// Public document type introspection + template listing (Session 105).
|
|
421
422
|
// Both are read-only projections over existing backoffice adapter calls
|
|
@@ -539,6 +540,7 @@ export const publicApiRoutes = async (app, opts) => {
|
|
|
539
540
|
contentService,
|
|
540
541
|
mediaService,
|
|
541
542
|
getContentNode: (id) => adapter.getContentNode(id),
|
|
543
|
+
mediaBaseUrl: resolvePublicBaseUrl(request),
|
|
542
544
|
}),
|
|
543
545
|
});
|
|
544
546
|
});
|
|
@@ -18,7 +18,8 @@ export declare function resolveSubtreeFilter(adapter: DatabaseAdapter, underId:
|
|
|
18
18
|
} | null>;
|
|
19
19
|
export declare function toSummaries(nodes: ContentNode[]): PublicContentNodeSummary[];
|
|
20
20
|
export declare function toPublicDocumentType(doc: DocumentTypeDefinition): PublicDocumentType;
|
|
21
|
-
export
|
|
21
|
+
export { resolvePublicBaseUrl } from "../config/public-url.js";
|
|
22
|
+
export declare function toPublicMediaFile(mediaService: MediaService, media: MediaFile, baseUrl: string): Promise<PublicMediaFile>;
|
|
22
23
|
export type PublishedItemDeps = {
|
|
23
24
|
adapter: DatabaseAdapter;
|
|
24
25
|
listSerializerContributions: () => Array<{
|
|
@@ -117,16 +117,39 @@ export function toPublicDocumentType(doc) {
|
|
|
117
117
|
})),
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// S186c Task 3, mandate 1: the PUBLIC surface (REST /api/v1/media* and
|
|
121
|
+
// GraphQL) is consumed cross-origin by design — a headless frontend's own
|
|
122
|
+
// server fetches `downloadUrl` directly (e.g. the blog starter's
|
|
123
|
+
// og-image route does `fetch(media.downloadUrl)`, and its post template
|
|
124
|
+
// renders `<img src={media.downloadUrl}>` on the FRONTEND's origin, not
|
|
125
|
+
// the CMS's). A path-relative URL there resolves against the wrong
|
|
126
|
+
// origin and 404s. The backoffice /api/media surface stays
|
|
127
|
+
// path-relative on purpose (routes/media.ts's toResponse) — it's always
|
|
128
|
+
// same-origin with the admin SPA that renders it.
|
|
129
|
+
//
|
|
130
|
+
// S186c Task 3 fix wave, L1: the actual base-resolution logic (and its
|
|
131
|
+
// full doc comment) moved to config/public-url.ts, mirroring
|
|
132
|
+
// config/preview.ts's previewBaseUrl idiom — re-exported here unchanged
|
|
133
|
+
// so every existing call site (public-api.ts, graphql/schema.ts) keeps
|
|
134
|
+
// importing it from this module.
|
|
135
|
+
export { resolvePublicBaseUrl } from "../config/public-url.js";
|
|
136
|
+
// The S105 public media projection. downloadUrl is the stable, computed
|
|
137
|
+
// app path (S186c §1/§3, `/media/<id>/<filename>`) made ABSOLUTE (see
|
|
138
|
+
// resolvePublicBaseUrl above) — NOT a time-limited presigned URL; it's
|
|
139
|
+
// recomputed on every call from id+filename rather than cached/stored,
|
|
140
|
+
// same convention as the REST media routes, but there is nothing here to
|
|
141
|
+
// expire. For a PRIVATE item the underlying path additionally carries a
|
|
142
|
+
// freshly minted `?token=` (MediaService.getDownloadUrl, S186c §3) —
|
|
143
|
+
// still recomputed fresh, still made absolute the same way.
|
|
144
|
+
export async function toPublicMediaFile(mediaService, media, baseUrl) {
|
|
145
|
+
const relativeUrl = await mediaService.getDownloadUrl(media.id);
|
|
124
146
|
return {
|
|
125
147
|
id: media.id,
|
|
126
148
|
filename: media.filename,
|
|
127
149
|
mimeType: media.mimeType,
|
|
128
150
|
size: media.size,
|
|
129
|
-
downloadUrl
|
|
151
|
+
downloadUrl: `${baseUrl}${relativeUrl}`,
|
|
152
|
+
visibility: media.visibility,
|
|
130
153
|
uploadedAt: media.uploadedAt,
|
|
131
154
|
};
|
|
132
155
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cpSync, existsSync, rmSync } from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
// Build-time only. The devDependency on @velora-cms/admin-ui exists purely
|
|
@@ -21,4 +21,12 @@ if (!existsSync(source)) {
|
|
|
21
21
|
}
|
|
22
22
|
rmSync(target, { recursive: true, force: true });
|
|
23
23
|
cpSync(source, target, { recursive: true });
|
|
24
|
+
// S186b: stamp the bundle with this build's own server version, so a
|
|
25
|
+
// scaffold that copies dist/admin-bundle out (or a syncAdminBundle() run
|
|
26
|
+
// later) inherits a version marker for boot-time drift detection
|
|
27
|
+
// (admin-static.ts) without re-deriving it. Resolved relative to this
|
|
28
|
+
// script, not cwd — same reasoning as `source`/`target` above.
|
|
29
|
+
const packageJson = JSON.parse(readFileSync(join(serverDist, "..", "package.json"), "utf8"));
|
|
30
|
+
writeFileSync(join(target, ".velora-version"), packageJson.version);
|
|
24
31
|
console.log(`[copy-admin-bundle] copied admin bundle -> ${target}`);
|
|
32
|
+
console.log(`[copy-admin-bundle] stamped version ${packageJson.version}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velora-cms/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22.12"
|
|
6
6
|
},
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"bin": {
|
|
12
|
-
"velora-server": "./dist/bin.js"
|
|
12
|
+
"velora-server": "./dist/bin.js",
|
|
13
|
+
"velora-sync-admin": "./dist/bin-sync-admin.js"
|
|
13
14
|
},
|
|
14
15
|
"main": "./dist/lib.js",
|
|
15
16
|
"exports": {
|
|
@@ -25,12 +26,12 @@
|
|
|
25
26
|
],
|
|
26
27
|
"repository": {
|
|
27
28
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/velora-cms/velora.git"
|
|
29
|
-
"directory": "apps/server"
|
|
29
|
+
"url": "git+https://github.com/velora-cms/velora-starter.git"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
|
-
"url": "https://github.com/velora-cms/velora/issues"
|
|
32
|
+
"url": "https://github.com/velora-cms/velora-starter/issues"
|
|
33
33
|
},
|
|
34
|
+
"homepage": "https://velora-cms.com",
|
|
34
35
|
"publishConfig": {
|
|
35
36
|
"access": "public"
|
|
36
37
|
},
|
|
@@ -58,11 +59,11 @@
|
|
|
58
59
|
"mercurius": "16.10.0",
|
|
59
60
|
"prisma": "6.19.3",
|
|
60
61
|
"zod": "4.4.3",
|
|
61
|
-
"@velora-cms/
|
|
62
|
-
"@velora-cms/
|
|
63
|
-
"@velora-cms/db-adapter": "0.11.0",
|
|
62
|
+
"@velora-cms/api-schemas": "0.12.0",
|
|
63
|
+
"@velora-cms/db-adapter": "0.11.2",
|
|
64
64
|
"@velora-cms/design-tokens": "0.11.0",
|
|
65
65
|
"@velora-cms/plugin-package": "0.11.0",
|
|
66
|
+
"@velora-cms/content-service": "0.11.0",
|
|
66
67
|
"@velora-cms/plugin-sdk": "0.11.0",
|
|
67
68
|
"@velora-cms/prisma-variants": "0.11.0"
|
|
68
69
|
},
|
|
@@ -75,12 +76,12 @@
|
|
|
75
76
|
"form-data": "4.0.6",
|
|
76
77
|
"tar": "^7.5.22",
|
|
77
78
|
"tsx": "4.22.4",
|
|
79
|
+
"@velora-cms/admin-ui": "0.1.0",
|
|
78
80
|
"@velora-cms/docs": "0.1.0",
|
|
79
81
|
"create-velora-plugin": "0.11.0",
|
|
80
|
-
"@velora-cms/admin-ui": "0.1.0",
|
|
81
|
-
"@velora-cms/google-maps": "0.1.0",
|
|
82
82
|
"velora-plugin": "0.11.0",
|
|
83
|
-
"@velora-cms/reading-time": "0.1.0"
|
|
83
|
+
"@velora-cms/reading-time": "0.1.0",
|
|
84
|
+
"@velora-cms/google-maps": "0.1.0"
|
|
84
85
|
},
|
|
85
86
|
"scripts": {
|
|
86
87
|
"build": "prisma generate && tsc -p . && node dist/scripts/generate-prisma-clients.js && node dist/scripts/copy-docs-bundle.js && node dist/scripts/copy-admin-bundle.js && node dist/scripts/pack-bundled-templates.js",
|
package/prisma/schema.prisma
CHANGED
|
@@ -60,14 +60,27 @@ model MediaFile {
|
|
|
60
60
|
filename String
|
|
61
61
|
mimeType String @map("mime_type")
|
|
62
62
|
size Int
|
|
63
|
-
// Object key inside the S3
|
|
64
|
-
// `url` is the canonical
|
|
65
|
-
//
|
|
63
|
+
// Object key inside the storage backend (S3/MinIO or the local driver) —
|
|
64
|
+
// what upload/download/delete address. `url` is the canonical stable app
|
|
65
|
+
// path persisted for API responses (`/media/<id>/<filename>`, S186c
|
|
66
|
+
// §1/§3). M3 fix wave: downloads do NOT go through presigned URLs
|
|
67
|
+
// anymore (that mechanism expired in 15 minutes even for public files,
|
|
68
|
+
// which S186c replaced) — GET /media/:id/:filename (media-public.ts)
|
|
69
|
+
// looks up this key server-side and streams the bytes itself, same as
|
|
70
|
+
// the backoffice /api/media route.
|
|
66
71
|
storageKey String @unique @map("storage_key")
|
|
67
72
|
url String
|
|
68
73
|
uploadedBy String @map("uploaded_by")
|
|
69
74
|
uploader User @relation(fields: [uploadedBy], references: [id])
|
|
70
75
|
uploadedAt DateTime @default(now()) @map("uploaded_at")
|
|
76
|
+
// 'public' | 'private' (S186c §2) — a flag, never a location: flipping it
|
|
77
|
+
// updates this row only, no file move, no key/URL change in either
|
|
78
|
+
// storage driver. Existing rows default/backfill to 'public', matching
|
|
79
|
+
// their pre-S186c behavior (everything was effectively link-accessible).
|
|
80
|
+
// Ships as core-migration step 0003_media_visibility (probe-guarded,
|
|
81
|
+
// additive) for pre-existing databases; new/db-push databases get it
|
|
82
|
+
// straight from this column.
|
|
83
|
+
visibility String @default("public")
|
|
71
84
|
|
|
72
85
|
@@index([uploadedBy])
|
|
73
86
|
@@map("media_files")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|