mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 16:08:58 -07:00
[store] prefix the img with avatar/
This commit is contained in:
@@ -203,7 +203,8 @@ inconsistency here without checking the client first.
|
|||||||
those four NULL and is rendered from `CurrentSaves` — one built Unity assetbundle per
|
those four NULL and is rendered from `CurrentSaves` — one built Unity assetbundle per
|
||||||
`BodyType`, which the client picks by the wearer's body. The row is the record as JSON, so an
|
`BodyType`, which the client picks by the wearer's body. The row is the record as JSON, so an
|
||||||
import is the export verbatim except that `CreatorAccountId` is forced to 1, the Coach — which
|
import is the export verbatim except that `CreatorAccountId` is forced to 1, the Coach — which
|
||||||
is what files it as stock content and whom a sale pays (`just cai-load` loads it;
|
is what files it as stock content and whom a sale pays — and each save's `ThumbnailFileName`
|
||||||
|
is put under `avatar/`, where this server serves it from (`just cai-load` loads it;
|
||||||
`apps/api/scripts/import-custom-avatar-items.ts` writes a migration; re-importing an id
|
`apps/api/scripts/import-custom-avatar-items.ts` writes a migration; re-importing an id
|
||||||
replaces it). Serve both through one shape: the shirt carries
|
replaces it). Serve both through one shape: the shirt carries
|
||||||
empty `CurrentSaves`/`Tags` and null `CustomBadgeMetadata`, never a missing key. The saves
|
empty `CurrentSaves`/`Tags` and null `CustomBadgeMetadata`, never a missing key. The saves
|
||||||
|
|||||||
@@ -89,14 +89,15 @@ Loads an export of `CustomAvatarItem` records (a JSON array, a `{ Results }` pag
|
|||||||
record) into the `custom_avatar_item` table, each stored as the row's JSON with its
|
record) into the `custom_avatar_item` table, each stored as the row's JSON with its
|
||||||
`CreatorAccountId` forced to 1, the Coach account, whatever the export said: that is what
|
`CreatorAccountId` forced to 1, the Coach account, whatever the export said: that is what
|
||||||
files it as stock content (the storefront tab searches by creator) and what a purchase
|
files it as stock content (the storefront tab searches by creator) and what a purchase
|
||||||
pays. It MERGES: an id already in the table has its row replaced, and nothing is deleted, so the
|
pays. Each save's `ThumbnailFileName` is put under `avatar/`, where this server serves
|
||||||
|
those images from (a name already prefixed is left alone). It MERGES: an id already in the table has its row replaced, and nothing is deleted, so the
|
||||||
players' own shirts, which share the table, are untouched. Re-running is safe. The load is
|
players' own shirts, which share the table, are untouched. Re-running is safe. The load is
|
||||||
verified by counting rows and probing ids from across the export; it fails loudly rather
|
verified by counting rows and probing ids from across the export; it fails loudly rather
|
||||||
than report a partial load. `--dry-run` reads and validates the export and prints the
|
than report a partial load. `--dry-run` reads and validates the export and prints the
|
||||||
statement count without writing.
|
statement count without writing.
|
||||||
|
|
||||||
The saves in an export name assetbundles and thumbnails by bare filename; the load stores
|
The saves in an export name their assetbundles by bare filename; the load stores the
|
||||||
the names only.
|
names only.
|
||||||
|
|
||||||
### `lookup` — print an account
|
### `lookup` — print an account
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,12 @@
|
|||||||
* For a load that needs no migration and no deploy — the usual case — use
|
* For a load that needs no migration and no deploy — the usual case — use
|
||||||
* `just admin cai-load` instead, which runs the same statements straight against a database.
|
* `just admin cai-load` instead, which runs the same statements straight against a database.
|
||||||
* The statements come from src/custom-avatar-items-load.ts either way: each record stored as
|
* The statements come from src/custom-avatar-items-load.ts either way: each record stored as
|
||||||
* the row's JSON minus `PurchaseInfo` and with `CreatorAccountId` forced to the Coach account
|
* the row's JSON minus `PurchaseInfo`, with `CreatorAccountId` forced to the Coach account (1)
|
||||||
* (1), an existing id REPLACED, nothing deleted.
|
* and each save's `ThumbnailFileName` put under `avatar/`, an existing id REPLACED, nothing
|
||||||
|
* deleted.
|
||||||
*
|
*
|
||||||
* The saves name assetbundles and thumbnails by bare filename; the migration does nothing
|
* The saves name assetbundles by bare filename; the migration does nothing about those files,
|
||||||
* about those files, which have to be served from wherever the client fetches them.
|
* which have to be served from wherever the client fetches them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync, writeFileSync } from 'node:fs'
|
import { readFileSync, writeFileSync } from 'node:fs'
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
* mapping with no imports, so both sides can use it.
|
* mapping with no imports, so both sides can use it.
|
||||||
*
|
*
|
||||||
* A row is the record as JSON (see 0022_custom_avatar_item_json.sql), so a load is the export
|
* A row is the record as JSON (see 0022_custom_avatar_item_json.sql), so a load is the export
|
||||||
* verbatim: no per-field mapping, and a field the source adds later rides along. Two things
|
* verbatim: no per-field mapping, and a field the source adds later rides along. Three things
|
||||||
* change on the way in: `PurchaseInfo`, a store-side projection the worker fills at read time,
|
* change on the way in: `PurchaseInfo`, a store-side projection the worker fills at read time,
|
||||||
* is stripped, and `CreatorAccountId` is FORCED to the Coach account — see
|
* is stripped; `CreatorAccountId` is FORCED to the Coach account; and each save's
|
||||||
* {@link customAvatarItemRowLiteral}.
|
* `ThumbnailFileName` is put under the `avatar/` prefix — see {@link customAvatarItemRowLiteral}.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +23,13 @@
|
|||||||
*/
|
*/
|
||||||
export const COACH_ACCOUNT_ID = 1
|
export const COACH_ACCOUNT_ID = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where a save's thumbnail is served from. The export names it bare
|
||||||
|
* (`f2y1ndzuvm5ke2hjmn4cwfwfl.png`); this server keeps those images under `avatar/` in the
|
||||||
|
* image bucket, so the name is prefixed on the way in and the client asks for the right key.
|
||||||
|
*/
|
||||||
|
export const SAVE_THUMBNAIL_PREFIX = 'avatar/'
|
||||||
|
|
||||||
/** The least an export record must carry to be a row. Everything else is kept as-is. */
|
/** The least an export record must carry to be a row. Everything else is kept as-is. */
|
||||||
export interface CustomAvatarItemExportRecord {
|
export interface CustomAvatarItemExportRecord {
|
||||||
CustomAvatarItemId: string
|
CustomAvatarItemId: string
|
||||||
@@ -60,18 +67,47 @@ export function readCustomAvatarItemExport(parsed: unknown): CustomAvatarItemExp
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One record as the SQL string literal its row holds: the JSON, minus `PurchaseInfo`, with
|
* A save's `ThumbnailFileName` under {@link SAVE_THUMBNAIL_PREFIX}. Idempotent — a name that
|
||||||
* `CreatorAccountId` overridden to {@link COACH_ACCOUNT_ID} whatever the export said. An
|
* already carries the prefix is left alone — so loading a file that was itself dumped from
|
||||||
|
* this table does not stack a second `avatar/`. Anything that is not a non-empty string
|
||||||
|
* (null, absent) is returned as it came.
|
||||||
|
*/
|
||||||
|
export function prefixSaveThumbnail(name: unknown): unknown {
|
||||||
|
if (typeof name !== 'string' || name === '') return name
|
||||||
|
return name.startsWith(SAVE_THUMBNAIL_PREFIX) ? name : SAVE_THUMBNAIL_PREFIX + name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One record as the SQL string literal its row holds: the JSON, minus `PurchaseInfo`, with two
|
||||||
|
* fields rewritten.
|
||||||
|
*
|
||||||
|
* `CreatorAccountId` is overridden to {@link COACH_ACCOUNT_ID} whatever the export said. An
|
||||||
* imported item is first-party content by definition, and the creator id is what makes it so
|
* imported item is first-party content by definition, and the creator id is what makes it so
|
||||||
* here: the storefront tab finds stock items by `creator_account_id = 1`
|
* here: the storefront tab finds stock items by `creator_account_id = 1`
|
||||||
* (`includeCoachItems=True`), and a sale pays the creator. An export from another server
|
* (`includeCoachItems=True`), and a sale pays the creator. An export from another server
|
||||||
* carries THAT server's creator ids — an item filed under one of those would vanish from the
|
* carries THAT server's creator ids — an item filed under one of those would vanish from the
|
||||||
* storefront into the user-generated tab, and its price would be paid to whichever local
|
* storefront into the user-generated tab, and its price would be paid to whichever local
|
||||||
* account happened to share the number.
|
* account happened to share the number.
|
||||||
|
*
|
||||||
|
* Each save's `ThumbnailFileName` is prefixed with {@link SAVE_THUMBNAIL_PREFIX}, which is where
|
||||||
|
* this server serves those images from. The rest of the save — the assetbundle names above
|
||||||
|
* all — is untouched.
|
||||||
*/
|
*/
|
||||||
export function customAvatarItemRowLiteral(record: CustomAvatarItemExportRecord): string {
|
export function customAvatarItemRowLiteral(record: CustomAvatarItemExportRecord): string {
|
||||||
const { PurchaseInfo: _purchaseInfo, ...stored } = record
|
const { PurchaseInfo: _purchaseInfo, ...stored } = record
|
||||||
const row = { ...stored, CreatorAccountId: COACH_ACCOUNT_ID }
|
const saves = Array.isArray(stored.CurrentSaves)
|
||||||
|
? stored.CurrentSaves.map((save: unknown) =>
|
||||||
|
save && typeof save === 'object' && !Array.isArray(save)
|
||||||
|
? {
|
||||||
|
...(save as Record<string, unknown>),
|
||||||
|
ThumbnailFileName: prefixSaveThumbnail(
|
||||||
|
(save as { ThumbnailFileName?: unknown }).ThumbnailFileName
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: save
|
||||||
|
)
|
||||||
|
: stored.CurrentSaves
|
||||||
|
const row = { ...stored, CreatorAccountId: COACH_ACCOUNT_ID, CurrentSaves: saves }
|
||||||
return `'${JSON.stringify(row).replaceAll("'", "''")}'`
|
return `'${JSON.stringify(row).replaceAll("'", "''")}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -269,10 +269,11 @@ const CUSTOM_AVATAR_ITEM_EXPORT = 'apps/econ/static/db/2025-1-cai.json'
|
|||||||
* Load the first-party custom avatar items into `custom_avatar_item` (owned by the `api`
|
* Load the first-party custom avatar items into `custom_avatar_item` (owned by the `api`
|
||||||
* worker). Like `runx catalog load`: the table's STRUCTURE is a migration, its first-party
|
* worker). Like `runx catalog load`: the table's STRUCTURE is a migration, its first-party
|
||||||
* CONTENTS are not, and this is the reload. Each record lands as the row's JSON with its
|
* CONTENTS are not, and this is the reload. Each record lands as the row's JSON with its
|
||||||
* `CreatorAccountId` forced to the Coach account (1) — what makes it stock content here — (the
|
* `CreatorAccountId` forced to the Coach account (1) — what makes it stock content here — and
|
||||||
* statements come from api's `custom-avatar-items-load.ts`, the same ones the migration
|
* each save's `ThumbnailFileName` put under `avatar/` (the statements come from api's
|
||||||
* generator writes), an id already present is REPLACED, and nothing is deleted — the players'
|
* `custom-avatar-items-load.ts`, the same ones the migration generator writes), an id already
|
||||||
* own shirts share the table and an export never mentions them. Re-running is always safe.
|
* present is REPLACED, and nothing is deleted — the players' own shirts share the table and an
|
||||||
|
* export never mentions them. Re-running is always safe.
|
||||||
*/
|
*/
|
||||||
const caiLoad = new Command('cai-load')
|
const caiLoad = new Command('cai-load')
|
||||||
.description('Load first-party custom avatar items from an export JSON (merges; never deletes)')
|
.description('Load first-party custom avatar items from an export JSON (merges; never deletes)')
|
||||||
|
|||||||
Reference in New Issue
Block a user