This commit is contained in:
seb
2026-08-01 01:59:05 +02:00
parent a05a4791d6
commit 59c874144b
6 changed files with 36 additions and 25 deletions

View File

@@ -156,7 +156,7 @@ Backend mapping:
sort, sort,
full: { w, h, size }, // AVIF, max 1000×1000 (stored as-is when already AVIF and within bounds) full: { w, h, size }, // AVIF, max 1000×1000 (stored as-is when already AVIF and within bounds)
thumb: { w, h }, // scaled AVIF, max 200×200 (fit inside) thumb: { w, h }, // scaled AVIF, max 200×200 (fit inside)
hash, // SHA-256 hex of full image bytes fullHash, // SHA-256 hex — use for GET /api/image/:fullHash and GET /api/thumb/:fullHash
thumbHash, // base64 thumbhash of thumb preview thumbHash, // base64 thumbhash of thumb preview
} }
``` ```
@@ -190,20 +190,25 @@ Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Err
## `pic` ## `pic`
Lookup by `pic.hash` (SHA-256 hex from `addImage`), not `thumbhash`. Lookup by `pic.hash` (`fullHash` — SHA-256 hex from `addImage.fullHash`), not `thumbhash`.
| Method | Signature | Returns | | Method | Signature | Returns |
| --- | --- | --- | | --- | --- | --- |
| `getImageByHash` | `(googleAuthId, hash)` | `{ buffer, w, h, size, contentType }` | | `getImageByHash` | `(googleAuthId, fullHash)` | `{ buffer, w, h, size, contentType }` |
| `getThumbByHash` | `(googleAuthId, hash)` | `{ buffer, w, h, contentType }` | | `getThumbByHash` | `(googleAuthId, fullHash)` | `{ buffer, w, h, contentType }` |
Auth via `articlepic` → `categoryarticle` → `resolveCategoryAccess`. Errors: `Image not found`, `Not authorized`. Auth via `articlepic` → `categoryarticle` → `resolveCategoryAccess`. Errors: `Image not found`, `Not authorized`.
`contentType` is always `image/avif`. In dashback: `contentType` is always `image/avif`.
```js ```js
const { buffer, contentType } = await store.pic.getImageByHash(req.user.id, hash); // GET /api/image/:fullHash
const { buffer, contentType } = await store.pic.getImageByHash(req.user.id, fullHash);
res.set('Content-Type', contentType).send(buffer); res.set('Content-Type', contentType).send(buffer);
// GET /api/thumb/:fullHash
const thumb = await store.pic.getThumbByHash(req.user.id, fullHash);
res.set('Content-Type', thumb.contentType).send(thumb.buffer);
``` ```
--- ---
@@ -224,8 +229,8 @@ res.set('Content-Type', contentType).send(buffer);
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` | | `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name })` | | `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name })` |
| `POST /api/article/:sku/image` | `article.addImage(googleAuthId, sku, imageBuffer)` | | `POST /api/article/:sku/image` | `article.addImage(googleAuthId, sku, imageBuffer)` |
| `GET /api/image/:hash` | `pic.getImageByHash(googleAuthId, hash)` | | `GET /api/image/:fullHash` | `pic.getImageByHash(googleAuthId, fullHash)` |
| `GET /api/image/:hash/thumb` | `pic.getThumbByHash(googleAuthId, hash)` | | `GET /api/thumb/:fullHash` | `pic.getThumbByHash(googleAuthId, fullHash)` |
--- ---

View File

@@ -133,12 +133,12 @@ const MENU = {
{ {
value: 'pic.getImageByHash', value: 'pic.getImageByHash',
label: 'getImageByHash', label: 'getImageByHash',
hint: 'googleAuthId, hash → meta', hint: 'googleAuthId, fullHash → meta',
}, },
{ {
value: 'pic.getThumbByHash', value: 'pic.getThumbByHash',
label: 'getThumbByHash', label: 'getThumbByHash',
hint: 'googleAuthId, hash → meta', hint: 'googleAuthId, fullHash → meta',
}, },
], ],
}, },
@@ -461,11 +461,14 @@ async function runMethod(client, action) {
case 'pic.getImageByHash': { case 'pic.getImageByHash': {
const googleAuthId = await askInt('google_auth id'); const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return; if (googleAuthId == null) return;
const hash = await askText('hash (SHA-256 hex)', { const fullHash = await askText('fullHash (SHA-256 hex)', {
validate: (v) => (!v?.trim() ? 'Required' : undefined), validate: (v) => (!v?.trim() ? 'Required' : undefined),
}); });
if (hash == null) return; if (fullHash == null) return;
const image = await client.pic.getImageByHash(Number(googleAuthId), hash.trim()); const image = await client.pic.getImageByHash(
Number(googleAuthId),
fullHash.trim(),
);
return [ return [
'result', 'result',
{ {
@@ -480,11 +483,14 @@ async function runMethod(client, action) {
case 'pic.getThumbByHash': { case 'pic.getThumbByHash': {
const googleAuthId = await askInt('google_auth id'); const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return; if (googleAuthId == null) return;
const hash = await askText('hash (SHA-256 hex)', { const fullHash = await askText('fullHash (SHA-256 hex)', {
validate: (v) => (!v?.trim() ? 'Required' : undefined), validate: (v) => (!v?.trim() ? 'Required' : undefined),
}); });
if (hash == null) return; if (fullHash == null) return;
const image = await client.pic.getThumbByHash(Number(googleAuthId), hash.trim()); const image = await client.pic.getThumbByHash(
Number(googleAuthId),
fullHash.trim(),
);
return [ return [
'result', 'result',
{ {

View File

@@ -68,7 +68,7 @@ export default function addImage(pool) {
w: processed.thumb.w, w: processed.thumb.w,
h: processed.thumb.h, h: processed.thumb.h,
}, },
hash: processed.full.hash, fullHash: processed.full.hash,
thumbHash: processed.thumb.hash, thumbHash: processed.thumb.hash,
}; };
} catch (err) { } catch (err) {

View File

@@ -4,12 +4,12 @@ import resolvePicAccess from './resolvePicAccess.js';
export default function getImageByHash(pool) { export default function getImageByHash(pool) {
const authorize = resolvePicAccess(pool); const authorize = resolvePicAccess(pool);
return async (googleAuthId, hash) => { return async (googleAuthId, fullHash) => {
await authorize(googleAuthId, hash); await authorize(googleAuthId, fullHash);
const result = await pool const result = await pool
.request() .request()
.input('hash', hash) .input('hash', fullHash)
.query(GET_FULL_BY_HASH); .query(GET_FULL_BY_HASH);
const row = result.recordset[0]; const row = result.recordset[0];

View File

@@ -4,12 +4,12 @@ import resolvePicAccess from './resolvePicAccess.js';
export default function getThumbByHash(pool) { export default function getThumbByHash(pool) {
const authorize = resolvePicAccess(pool); const authorize = resolvePicAccess(pool);
return async (googleAuthId, hash) => { return async (googleAuthId, fullHash) => {
await authorize(googleAuthId, hash); await authorize(googleAuthId, fullHash);
const result = await pool const result = await pool
.request() .request()
.input('hash', hash) .input('hash', fullHash)
.query(GET_THUMB_BY_HASH); .query(GET_THUMB_BY_HASH);
const row = result.recordset[0]; const row = result.recordset[0];

View File

@@ -4,10 +4,10 @@ import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
export default function resolvePicAccess(pool) { export default function resolvePicAccess(pool) {
const access = resolveCategoryAccess(pool); const access = resolveCategoryAccess(pool);
return async (googleAuthId, hash) => { return async (googleAuthId, fullHash) => {
const result = await pool const result = await pool
.request() .request()
.input('hash', hash) .input('hash', fullHash)
.query(GET_CATEGORY_ID_BY_HASH); .query(GET_CATEGORY_ID_BY_HASH);
const categoryId = result.recordset[0]?.categoryId; const categoryId = result.recordset[0]?.categoryId;