Maj 05-11
Added
Server vote webhooks — Servers now have full webhook parity with bots. A new
serverWebhookstable was added to the database, along with a complete CRUD API at/api/servers/[id]/webhooks(GET, POST, DELETE). Every time a user votes for a server, a signed HTTP POST is sent to the configured URL with a HMAC-SHA256X-Signatureheader so receivers can verify the payload is authentic.Vote Webhook UI in server dashboard — Server owners can now manage their vote webhook directly from the dashboard. The new card lets you paste a webhook URL, optionally set or rotate the signing secret, save or remove the configuration, and see an "Active" badge when a webhook is configured. Success and error feedback is shown inline without a page reload.
Anonymous votes trigger webhooks — Previously, vote webhooks were only dispatched when the voter was authenticated. The webhook call has been moved outside the session guard, so anonymous votes now fire the webhook correctly just like logged-in votes do.
Dedicated
CRON_SECRETfor cron job authentication — All/api/cron/*endpoints previously validated incoming requests usingSESSION_SECRET. A separateCRON_SECRETenvironment variable has been introduced so cron credentials are isolated from session credentials. Update your Railway / hosting environment variables accordingly.
Fixed
Webhook HMAC signing was broken — The
secret_hashcolumn was storing a SHA-256 hash of the secret instead of the raw value. Because HMAC-SHA256 signing requires the original secret, all signatures were being computed incorrectly. The column has been renamed tosecretand now stores the plaintext value. Existing webhook configs will need to be re-saved.Raw database errors leaked to clients — When a Drizzle or Postgres query failed, the full exception (including table names, column names, and query fragments) was serialised into the API response. All server-side handlers now catch these low-level errors and return a generic
500 Internal Server Errormessage instead.Rate limiting could be bypassed with an unknown IP — The vote rate-limiter built its Redis key as
vote:<ip>. When the client IP could not be detected, the key fell back tovote:unknown, meaning every request from an undetectable IP shared the same bucket and effectively got unlimited votes. Requests where the IP cannot be determined now receive400 Bad Requestimmediately.46+ empty
catchblocks swallowed all errors silently — Across the entire API layer, exceptions were being caught and discarded with.catch(() => {}). This made it impossible to diagnose failures in Railway logs. Every instance has been replaced with.catch((err) => console.error('[tag] ...', err))so errors are always visible.useAuthfailed silently on authentication errors — ThefetchUser()function insideuseAuth.tshad an empty error handler, so any failure during session hydration (expired token, network error, etc.) produced no output whatsoever. The catch block now logs[useAuth] fetchUser failed:with the full error.Discord notifications were fire-and-forget with no error visibility — Bot and server approval/rejection notifications sent via the Discord bot had no
.catch()handler, so a failed DM or channel message would disappear without a trace. All notification calls now log failures withconsole.error('[notify] ...').
Refactored
- Removed
anytypes from three Vue files — Untypedanyreferences were replaced with explicit TypeScript interfaces to improve type safety and catch bugs at compile time:admin/auctions.vue— newAuctionSlotandAuctionBidinterfaces; all$fetchcalls, modal refs, and helper functions (isLive,isUpcoming,openEditModal,pickWinner) are now fully typed.compare.vue— newCompareItem,CompareRating, andCompareResultinterfaces;winner(),itemAvatar(), and all$fetchcalls are typed.CollectionCard.vue— newCollectionandCollectionOwnerinterfaces;defineProps<{ collection: any }>()replaced with a proper typed prop.
- Removed