Commit Graph
1788 Commits
Author SHA1 Message Date
openhands a4bcd40e7b docs(catalog-sync): surface the bundle extension and the page-writing nuance
`.hab` has been the stored bundle extension since the `FURNI_BUNDLE_EXT`
switch, so the repair path already writes `.hab` (the sources serve `.nitro`),
but nothing in the output said so. Print the resolved extension in the run
header and correct the header comment: generating the catalog SQL also creates
the `catalog_pages` sub-pages it references, so a "generate only" run is not
entirely read-only — only the `catalog_items` rows wait for `--apply-sql`.
2026-10-15 19:53:29 +02:00
openhands 4b575d3a1d feat(catalog): add scripts/catalog-sync.ts CLI + interactive menu
Updating the catalog was only possible from the admin "Catalog audit" page,
which streams `runCatalogAudit` over SSE and always compares against every
enabled clone source. This adds the same engine to the terminal so it can be run
by hand or from cron:

- `scripts/catalog-sync.ts` — CLI driver around `runCatalogAudit`. Prints the
  live progress, the per-source "on the source but not in this hotel" counts and
  a summary block; generated catalog SQL is saved to
  `storage/catalog-sync/*.sql` (gitignored). Supports `--list-sources`,
  `--sources=<a,b>`, `--icons`, `--nitros`, `--furnidata`, `--structure`,
  `--check-ids`, `--sql`, `--apply-sql`, `--organize-sql`, `--all`, `--json` and
  `--fail-on-errors`.
- `scripts/catalog-sync.sh` — interactive menu (`pnpm catalog:sync`). Lists the
  configured sources (so the menu always matches `clone_sources`), lets Habbo
  (COM) be taken with Enter, and offers audit-only through full repair +
  applying the catalog SQL, each with a distinct confirmation. With no arguments
  it needs a terminal; with any flag it passes straight through to the CLI.
- `runCatalogAudit` gains `sourceIds` to restrict which sites are compared;
  omitting it keeps the previous "compare against everything enabled" behaviour.

An audit writes nothing, and `--apply-sql` is the only flag that changes the
catalog. Verified against the live DB: 16370 items_base rows, 16479 catalog
items, 2871 Habbo (COM) entries missing from this hotel.
2026-10-15 18:48:42 +02:00
openhands 61107848f8 fix(imaging): normalise avatar figures before building URLs
Legacy accounts can carry a malformed stored look — the old register default
`hr-100-.hd-180-1...` has an empty colour slot — which the imager rejects with
400 "Invalid figure format", so the avatar renders broken everywhere the look
appears. Normalise the figure in the shared URL builder (`getAvatarUrl`, used by
every avatar on the site): an unrenderable look becomes `DEFAULT_AVATAR_FIGURE`
while the proxy route keeps rejecting malformed input with 400.

The rule set and default now live in one client-safe module
(`src/lib/avatar-figure.ts`), shared by the URL builder and the server parser.
2026-10-15 18:00:05 +02:00
openhands 1e3406a7ae fix(furni): keep classname env read out of the browser bundle
`src/lib/furni/classname.ts` is imported by client components for its pure
string helpers, but it read `process.env.FURNI_BUNDLE_EXT` at module load.
In any client bundle without a `process` shim — including the UI fixture's
esbuild build — that threw `ReferenceError: process is not defined`, so the
lazily-loaded organize-imports dialog never mounted and `pnpm test:ui`
failed (studio.spec.ts). Fall back to the default extension when `process`
is absent; server behaviour is unchanged.
2026-10-15 16:39:45 +02:00
openhands ef6c671903 test(ci): deterministic deploy gate + advisory quality suite; sync README versions
The deploy gate ran e2e specs that depend on live content and data, so a missing
upload (the /help article images) or a legacy malformed avatar figure could turn
a safe code deploy red. Split the suites by what they actually guarantee:

- Blocking gate (`pnpm test:e2e`, `playwright.config.ts`): only checks the CMS
  can guarantee itself — anonymous redirects, register form fields, the captcha
  DOM contract and the cache-control headers (deterministic origin-direct).
- Advisory suite (`pnpm test:e2e:quality`, `playwright.quality.config.ts`):
  broken images, layout overflow and warm-page latency, which depend on live
  content/upload state and the host. It runs as a `continue-on-error` CI job
  against the public site so a real content problem stays visible without
  blocking the pipeline.

Also add `scripts/sync-readme-versions.mjs` (+ tests, docs and a CI check) so
the pinned versions in README.md can never drift from package.json / .nvmrc.
2026-10-15 16:26:47 +02:00
openhands ef2f8c30f0 fix(register): use a valid default avatar figure
The starter look for a new account was `hr-100-.hd-180-1...`, whose empty
hair colour slot is rejected by the imager with 400 "Invalid figure format".
Every player who registered without a look therefore got a broken avatar.
Add the missing colour slot (`hr-100-1`), which the imager renders.
2026-10-15 16:26:31 +02:00
openhands 73005a3c05 docs(readme): refresh pinned versions in the header
Match the versions table and core-stack line to package.json after the
dependency bump: pnpm 12.11.2, Next.js 16.4.1, React 19.3.1,
Drizzle ORM 0.45.4, Zod 4.6.7, Vitest 5.0.4, Playwright 1.64.1.
2026-10-15 16:06:54 +02:00
openhands 918134df69 docs(readme): add a clear Cloudflare Turnstile setup section
Explain where the captcha is configured, how the admin panel and env
vars resolve (with a precedence table), step-by-step Turnstile setup,
that the site key is public while the secret must never be committed,
and how to verify. Also link it from Optional Integrations.
2026-10-15 16:05:49 +02:00
openhands d64d864175 docs(env): clarify captcha provider setup and key handling
Document that CAPTCHA_PROVIDER accepts a provider name (e.g. turnstile)
and spell out that the site key is public while the secret must stay on
the host / in CI and never be committed.
2026-10-15 16:03:17 +02:00
openhands 62de3e3a8b test: cover captcha env fallback, cache TTLs and public e2e flows
- unit: captcha env fallback/precedence (DB wins, explicit "none") and
  perf-ttls defaults, overrides and invalid values
- e2e: public page render + image health, register form and Turnstile
  widget consistency, anonymous auth-guard redirects, plus warm latency
  budgets and cache-control contracts
2026-10-15 16:00:08 +02:00
openhands 9f024dc6ac perf(app): batch catalog updates, tune caches, add captcha env fallback
- catalog: apply bulk offer edits in a single transaction with one RCON
  refresh instead of N round-trips, virtualize the items table past 150
  rows and cap the shop preview at 200 tiles
- cache: lift the public cache TTLs and the boot warm-up into env-tunable
  constants (src/lib/perf-ttls.ts; CMS_*_TTL_SEC, CMS_WARMUP_ENABLED)
- auth: resolve the captcha provider, site key and secret from
  CAPTCHA_PROVIDER, TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY when the
  admin settings are unset; an explicit DB value (including "none") still
  wins, so Turnstile can be enabled per-deploy without touching the DB.
2026-10-15 16:00:00 +02:00
openhands 05c2706eaf chore(deps): bump runtime and dev dependencies
next 16.4.0 -> 16.4.1, react/react-dom 19.3.0 -> 19.3.1,
@tanstack/react-query 5.104.2 -> 5.104.3,
@tanstack/react-virtual 3.14.17 -> 3.14.19, lucide-react 1.55.0 -> 1.57.0,
motion 14.1.0 -> 14.2.0, mysql2 3.24.5 -> 3.24.6, resend 6.32.1 -> 6.33.0,
tinymce 8.9.3 -> 8.10.0, music-metadata 12.1.1 -> 12.2.1,
@playwright/test 1.64.0 -> 1.64.1, postcss 8.5.29 -> 8.5.30,
testcontainers 12.2.0 -> 12.2.1.
2026-10-15 15:59:54 +02:00
openhands ac23d7d1d9 fix(ops): treat a CI-managed host as a skip, not a failed updater run
Commit 3c8abb6f made docker-update.sh exit 0 (SKIP) on a host owned by CI so
the cron does not report a failed deployment. The tests still expected a
nonzero exit for the ci-active/ci-green/ci-upstream scenarios, so five cases
failed.

- docker-update.test.ts: move the CI scenarios out of the non-zero group and
  assert the skip: exit 0, 'This host is managed by CI' in the output and no
  'compose up'. All three CI scenarios (both slot containers and the nginx
  upstream) now share one test.
- README.md: document the exit-0 skip.
2026-10-14 21:59:42 +02:00
openhands 3c8abb6fbf feat(assets): choose furniture bundle ext via FURNI_BUNDLE_EXT
Bundle the remaining imaging hardening and the host/CI fixes from the
"Doe alles" pass into one release.

Furniture:
- Resolve the local bundle extension from FURNI_BUNDLE_EXT (".hab" default,
  ".nitro" for a stock Nitro client). The configured extension is written by
  imports/repairs and preferred on lookup; the other stays a legacy fallback.

Imaging:
- Avatar proxy sends s-maxage=86400 + stale-while-revalidate (300/3600 for the
  fallback path) so the edge can hold a render a day.
- Cloudflare cache rule now covers /api/imaging/ (verified MISS->HIT live).
- Document that bare /imaging is shadowed by the edge proxy to the renderer.

Ops/CI:
- docker-update.sh skips a CI-managed host (exit 0) instead of failing cron.
- ci-deploy.sh runs the cross-browser imaging gate (chromium+firefox+webkit)
  against the candidate before cutover; DEPLOY_SKIP_CROSS_BROWSER=1 as escape.
- scripts/imager-health-alert.sh alerts (Discord) on imager up/down transitions;
  runs from cron every 5 minutes.

Tests:
- Furniture bundle-ext resolver + FURNI_BUNDLE_EXT wiring.
- Imaging performance budget (bytes + warm latency).
- classifyDdos: /api/imaging/* stays in the api bucket.
2026-10-14 21:46:56 +02:00
openhands 640e5a5dd0 test(imaging): broaden coverage across contract, transcode, routing and pixels
Environment-independent contract tests (cache headers + source, CORS, OPTIONS
preflight, negative figure/badge tables, concurrency burst) run in both the
deploy gate and the cross-browser job.

CMS-only /imaging assertions (webp/avif transcode, animated-APNG passthrough,
s-maxage edge caching) live in cms-imaging.spec.ts and are skipped behind the
edge proxy; the public topology has its own public-routing.spec.ts smoke that
fails if the edge ever stops fronting the imager or starts shadowing the CMS
endpoints. A committed per-engine visual baseline pins the static render.

The cross-browser imaging job is now blocking (IMAGING_EDGE=1), so avatar
imaging on chromium/firefox/webkit is a required signal.
2026-10-14 21:19:18 +02:00
openhands 3fa643828d test(imaging): drop /imaging e2e assertion shadowed by the edge proxy
In production the edge proxy fronts /imaging to the imager container, so the
CMS route only answers when reached directly. Asserting it in the shared spec
failed against the public site in all three engines. Its behaviour is covered
by src/lib/imaging-route.test.ts; the e2e spec now only checks the endpoints
that are identical in both topologies (/api/imaging/avatar and the badge).
2026-10-14 21:05:36 +02:00
openhands 888765c47f test(imaging): add endpoint contract e2e and cross-browser render checks
The deploy gate only installs chromium, so imaging had no browser-level
coverage. Add two Playwright specs:

- e2e/imaging/avatar-endpoint.spec.ts: HTTP contract for the avatar
  (/api/imaging/avatar), default (/imaging) and badge endpoints — animated
  APNG for a figure with an effect, the trailing-`.0` regression figure,
  ETag/304 revalidation, and a 400 for a malformed figure. Runs in the
  deploy gate automatically via the default Playwright config.
- e2e/imaging/avatar-render.spec.ts: loads the avatar in a real browser and
  asserts it decodes at a non-zero size and animates. Navigates to the image
  URL directly so production CSP `upgrade-insecure-requests` cannot rewrite
  the request over a plain-HTTP host.

Add playwright.imaging.config.ts running both across chromium, firefox and
webkit, a `test:imaging` script, and a non-blocking CI job that installs all
three engines and checks the live site.
2026-10-14 21:00:27 +02:00
openhands f3407ebfdf fix(imaging): support .hab assets, cache avatars/badges, harden routes
The hotel moved from .nitro to .hab bundles, so the imager must treat .hab
like .nitro and the public avatar endpoints must accept the figure strings
the client sends (including extra trailing segments such as `.0`, which the
old FIGURE_RE rejected with a 400).

- imager-upstream: relax FIGURE_RE, extend parseAvatarQuery test coverage
- api/imaging/avatar + /imaging: shared validation, placeholder and ETag/304
- api/imaging/badge: single-flight upstream fetch and conditional responses
- imager-http/imager-png: strong ETag helpers and APNG passthrough guard
- csp + next/image: unoptimized external images, tightened CSP
- deps: override postcss-selector-parser to ^7.1.6
- also fold in the pending drizzle draft snapshot and stray test cleanup
2026-10-14 20:40:50 +02:00
openhands 56676effc3 feat(studio): localize catalog studio UI and polish responsive layout
Route all Catalog Studio strings through the admin.studio namespace
(workspace, filters, review, inspector, quality, sync, scale32, organize
imports and page titles/subtitles) with English fallback, and add shared
StudioPage chrome plus a loading skeleton. Tighten mobile padding on the
header and sub-bars.
2026-10-13 20:31:26 +02:00
openhands d7d6382b60 chore: upgrade msw to v3 and update typescript 2026-10-12 20:20:27 +02:00
openhands 007818b03f chore: upgrade pnpm version via corepack 2026-10-12 20:18:32 +02:00
openhands b24045812b chore: update dependencies (vitest, drizzle, zod, lucide) 2026-10-12 20:16:24 +02:00
openhands 0d943fe3dc fix(ci): use runs-on: ubuntu-latest (non-empty) for Gitea Actions v5 2026-10-12 20:05:56 +02:00
openhands 6e82a02e64 fix(ci): rewrite ci.yaml with correct Gitea Actions syntax 2026-10-12 20:04:13 +02:00
openhands 08ff14d0ac test: trigger CI run for monitoring 2026-10-12 20:01:17 +02:00
openhands b17da6ab25 fix(gitea): use runs-on: [] so host runner picks up jobs (contract test aligned) 2026-10-12 19:43:34 +02:00
openhands 56ca775405 fix(gitea): runner label ubuntu-latest (remove GITEA_TOKEN drop-in conflict) 2026-10-12 19:38:08 +02:00
openhands 8ee99e8ce8 test: ubuntu-latest only label 2026-10-12 19:26:16 +02:00
openhands 5ea1d50ad1 test: add ubuntu-latest label test 2026-10-12 19:24:54 +02:00
openhands 6ef7486ca4 test: clean v5 install via Docker approach 2026-10-12 19:21:39 +02:00
openhands 7c15cfd26d test: clean v5 install test 2026-10-12 19:19:31 +02:00
openhands 2ca277d858 test: final CI test after clean install 2026-10-12 19:17:52 +02:00
openhands 758e25452d test: try different label approach 2026-10-12 19:13:19 +02:00
openhands 60d375144d test: final CI trigger test 2026-10-12 19:12:03 +02:00
openhands 1c816019b1 test: final CI verification 2026-10-12 19:09:59 +02:00
openhands ceff6f98d9 test: add test-ci file for CI trigger 2026-10-12 19:09:01 +02:00
openhands 8395e60f87 test: fourth verification after token fix 2026-10-12 19:07:43 +02:00
openhands a92b951540 test: token sync fix verification 2026-10-12 19:06:50 +02:00
openhands d5938f94ed test: second verification 2026-10-12 19:05:45 +02:00
openhands 991328ff0e test: verify runner picks up Gitea CI 2026-10-12 19:02:45 +02:00
openhands 1835221096 fix: remove .gitlab-ci.yml (using Gitea CI instead) 2026-10-12 19:00:45 +02:00
openhands e7a6245ac2 test: add test line to README 2026-10-12 18:49:25 +02:00
openhands da43b6c80c fix: use explicit runner label self-hosted:host to match registered v5-runner 2026-10-12 18:28:31 +02:00
openhands eb67424a1c test: add verification comment to .gitlab-ci.yml, triggering workflow run 2026-10-12 18:25:05 +02:00
openhands 3776475db4 fix: ci-workflow-contract test expectations updated to runs-on: [] 2026-10-12 18:18:24 +02:00
openhands 54b9186e52 fix: test.yml runs-on ubuntu-latest → [] for host runner compatibility 2026-10-12 18:15:58 +02:00
openhands fe684faf70 fix: change Gitea Actions runner from self-hosted to host runner, preventing stuck jobs when self-hosted runners are offline 2026-10-12 18:08:08 +02:00
openhands 22f834ef45 fix: add explicit runner tags to .gitlab-ci.yml jobs, ensuring shared runners are used and 'no matching online runner' error is prevented 2026-10-12 17:46:36 +02:00
openhands 7b249a7222 P1-P3 admin cleanup: rank guard, SSRF guard, count caching, dead code removal, pagination rewrite
- P1: new src/lib/admin/rank-guard.ts with tryGuardRank/guardRank + RankGuardTarget/Result; extracted from users.ts; applied to admin-bans (create/lift + rank redirect) and admin-badges (caps + rank guard + error surfacing); import-url-safety.ts SSRF guard wired into clone save+icon fetch; logAudit await in help-tickets; antiddos mutations SETTINGS_EDIT; markAllAlertsRead NOTIFICATIONS_EDIT; rankError/saveError in all 25 message files; tests rew/new for bans/badges/rank-guard/import-url-safety

- P2: ticket-inbox merged pagination reworked to single UNION ALL SQL with LIMIT/OFFSET (avoids O(offset) materialization); getCachedAdminCount with cacheFiltered option applied to devops/errors/photos/items/guilds/help-tickets/moderation pages; admin-theme catches→?error=1+banner+25 locales; logger in silent catches (rare-values/writeable-boxes); catalog-items-loader CAST→plain equality; count caching helper extended with opt-in cacheFiltered

- P3: removed dead modules is-staff/notice/recent-furni-resync/log-filters plus load-user-sanctions/user-sanction-timeline; removed orphaned CATALOG_PAGE_COLUMNS re-export; removed getFurniIconUrl from studio-furni-icons

Build: lint 0, typecheck 0, 3480 tests pass, i18n:check exit 0, colors exit 0. Circular chunk warning (9452/10535/66463) is webpack code-splitting artifact; build succeeds.
2026-10-12 17:42:19 +02:00
openhands 57529fd1ea Fix: badge API now reads from Gamedata/c_images/album1584
The badge GIF files exist at /var/www/Gamedata/c_images/album1584/ but the
/api/imaging/badge endpoint only looked in /swf/c_images/album1584/ (relative
to process.cwd()). Added GEDATA_IMAGES_BASE candidate and fetchCandidate
handler so badges render correctly in the admin UI and on the site.
2026-10-11 21:14:10 +02:00