fix(blog/admin): accept upload URLs (absolute + relative /uploads paths)
All checks were successful
Build & Deploy API / build-and-deploy (push) Successful in 1m15s
All checks were successful
Build & Deploy API / build-and-deploy (push) Successful in 1m15s
L'upload renvoie maintenant une URL absolue construite depuis APP_URL (`https://app.rubis.pro/api/v1/uploads/blog/{uuid}.{ext}`), pour que la landing publique l'affiche directement en <img src> sans absolutize. Le validator post (createPostValidator + updatePostValidator) accepte : * Les URLs HTTPS absolues (image externe ou notre upload absolutisé) * Les paths relatifs `/api/v1/uploads/...` (rétro-compat sécurité — si une URL relative arrive d'une autre source, on la laisse passer plutôt que 422 sur un champ qui résout côté client) Bug initial : POST /api/v1/admin/uploads renvoyait `/api/v1/uploads/...` (relatif), puis le PATCH /admin/posts/:id rejetait ce path en 422 car `vine.string().url()` exige une URL absolue. Cause = double oubli (path relatif côté upload + validator strict). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
52bc7507fb
commit
b2dd991c58
@ -59,8 +59,13 @@ export async function uploadBlogImage(file: MultipartFile): Promise<UploadResult
|
||||
}
|
||||
await drive.use().moveFromFs(file.tmpPath, storageKey)
|
||||
|
||||
// URL absolue pour que la landing publique (rubis.pro) puisse l'afficher
|
||||
// directement en <img src> sans avoir à connaître l'API host. APP_URL est
|
||||
// posé par le ConfigMap k3s rubis-api-config ('https://app.rubis.pro').
|
||||
const apiHost = (process.env.APP_URL || 'http://localhost:3333').replace(/\/$/, '')
|
||||
|
||||
return {
|
||||
publicPath: `/api/v1/uploads/blog/${filename}`,
|
||||
publicPath: `${apiHost}/api/v1/uploads/blog/${filename}`,
|
||||
storageKey,
|
||||
contentType: extToContentType(ext),
|
||||
sizeBytes: file.size,
|
||||
|
||||
@ -18,6 +18,20 @@ const title = () => vine.string().minLength(5).maxLength(80)
|
||||
const excerpt = () => vine.string().minLength(80).maxLength(280)
|
||||
const tags = () => vine.array(vine.string().maxLength(50)).maxLength(5).distinct()
|
||||
|
||||
/**
|
||||
* URL d'image acceptée pour `hero_image_url` / `og_image_url` :
|
||||
* - URL absolue HTTPS (image externe — rare, mais possible)
|
||||
* - Path relatif commençant par /api/v1/uploads/... (uploads MinIO via
|
||||
* notre endpoint `BlogUploadsController.show`)
|
||||
*
|
||||
* On refuse `http://` (sécurité mixed content) et autres chemins relatifs.
|
||||
*/
|
||||
const imagePathOrUrl = () =>
|
||||
vine
|
||||
.string()
|
||||
.maxLength(500)
|
||||
.regex(/^(https:\/\/.+|\/api\/v1\/uploads\/.+)$/)
|
||||
|
||||
/**
|
||||
* Création d'un brouillon. On accepte `status` mais c'est toujours `draft`
|
||||
* à la création — la publication passe par /publish.
|
||||
@ -30,9 +44,9 @@ export const createPostValidator = vine.compile(
|
||||
contentMd: vine.string().minLength(50),
|
||||
tags: tags().optional(),
|
||||
authorName: vine.string().minLength(2).maxLength(100).optional(),
|
||||
heroImageUrl: vine.string().url().nullable().optional(),
|
||||
heroImageUrl: imagePathOrUrl().nullable().optional(),
|
||||
heroImageAlt: vine.string().maxLength(250).nullable().optional(),
|
||||
ogImageUrl: vine.string().url().nullable().optional(),
|
||||
ogImageUrl: imagePathOrUrl().nullable().optional(),
|
||||
canonicalUrl: vine.string().url().nullable().optional(),
|
||||
noindex: vine.boolean().optional(),
|
||||
}),
|
||||
@ -50,9 +64,9 @@ export const updatePostValidator = vine.compile(
|
||||
contentMd: vine.string().minLength(50).optional(),
|
||||
tags: tags().optional(),
|
||||
authorName: vine.string().minLength(2).maxLength(100).optional(),
|
||||
heroImageUrl: vine.string().url().nullable().optional(),
|
||||
heroImageUrl: imagePathOrUrl().nullable().optional(),
|
||||
heroImageAlt: vine.string().maxLength(250).nullable().optional(),
|
||||
ogImageUrl: vine.string().url().nullable().optional(),
|
||||
ogImageUrl: imagePathOrUrl().nullable().optional(),
|
||||
canonicalUrl: vine.string().url().nullable().optional(),
|
||||
noindex: vine.boolean().optional(),
|
||||
}),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user