diff --git a/.env.example b/.env.example index b107db7d..f87a4bb7 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,10 @@ AUTH_SECRET= AUTH_DISCORD_ID= AUTH_DISCORD_SECRET= -ADMINS= \ No newline at end of file +ADMINS= + +# Pterodactyl Application API key used to auto-tag Birdflop-hosted listings +# on the server list. Optional; detection is skipped when unset. +PANEL_API_KEY= +# Defaults to https://panel.birdflop.com when unset. +PANEL_URL= diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 89ee2a90..f0f00711 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -7,6 +7,7 @@ on: paths: - 'packages/rgbirdflop/**' - 'packages/analyze/**' + - 'packages/mc-status/**' - '.release-please-manifest.json' - 'release-please-config.json' @@ -29,15 +30,15 @@ jobs: manifest-file: .release-please-manifest.json - name: Checkout code - if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] }} + if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] || steps.release.outputs['packages/mc-status--release_created'] }} uses: actions/checkout@v7 - name: Setup pnpm - if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] }} + if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] || steps.release.outputs['packages/mc-status--release_created'] }} uses: pnpm/action-setup@v6 - name: Setup Node.js - if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] }} + if: ${{ steps.release.outputs['packages/rgbirdflop--release_created'] || steps.release.outputs['packages/analyze--release_created'] || steps.release.outputs['packages/mc-status--release_created'] }} uses: actions/setup-node@v6 with: node-version: 24 @@ -86,3 +87,24 @@ jobs: # it is currently imperative to set NODE_AUTH_TOKEN to an empty string for OIDC auth to work # https://github.com/actions/setup-node/blob/633bb92bc0aabcae06e8ea93b85aecddd374c402/src/authutil.ts#L57 NODE_AUTH_TOKEN: '' + + # mc-status + + - name: Install dependencies (mc-status) + if: ${{ steps.release.outputs['packages/mc-status--release_created'] }} + working-directory: packages/mc-status + run: pnpm install + + - name: Build package (mc-status) + if: ${{ steps.release.outputs['packages/mc-status--release_created'] }} + working-directory: packages/mc-status + run: pnpm build + + - name: Publish to npm (mc-status) + if: ${{ steps.release.outputs['packages/mc-status--release_created'] }} + working-directory: packages/mc-status + run: npm publish --provenance --access public + env: + # it is currently imperative to set NODE_AUTH_TOKEN to an empty string for OIDC auth to work + # https://github.com/actions/setup-node/blob/633bb92bc0aabcae06e8ea93b85aecddd374c402/src/authutil.ts#L57 + NODE_AUTH_TOKEN: '' diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 24031fc9..eedc59f2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,5 @@ { "packages/rgbirdflop": "0.4.1", - "packages/analyze": "0.4.0" + "packages/analyze": "0.4.0", + "packages/mc-status": "1.0.0" } diff --git a/drizzle/migrations/0006_reflective_tarantula.sql b/drizzle/migrations/0006_reflective_tarantula.sql new file mode 100644 index 00000000..de590680 --- /dev/null +++ b/drizzle/migrations/0006_reflective_tarantula.sql @@ -0,0 +1,56 @@ +CREATE TABLE `serverReports` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `serverId` integer NOT NULL, + `reason` text NOT NULL, + `details` text, + `reporterId` text, + `ip` text, + `resolved` integer DEFAULT false NOT NULL, + `createdAt` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`serverId`) REFERENCES `servers`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`reporterId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null +); +--> statement-breakpoint +CREATE INDEX `serverReports_server_idx` ON `serverReports` (`serverId`);--> statement-breakpoint +CREATE INDEX `serverReports_resolved_idx` ON `serverReports` (`resolved`);--> statement-breakpoint +CREATE TABLE `serverVotes` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `serverId` integer NOT NULL, + `username` text NOT NULL, + `ip` text, + `votifierDelivered` integer DEFAULT false NOT NULL, + `createdAt` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`serverId`) REFERENCES `servers`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `serverVotes_server_created_idx` ON `serverVotes` (`serverId`,`createdAt`);--> statement-breakpoint +CREATE INDEX `serverVotes_username_idx` ON `serverVotes` (`username`);--> statement-breakpoint +CREATE TABLE `servers` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `ownerId` text, + `name` text NOT NULL, + `slug` text NOT NULL, + `description` text NOT NULL, + `shortDescription` text, + `edition` text DEFAULT 'java' NOT NULL, + `javaHost` text, + `javaPort` integer, + `bedrockHost` text, + `bedrockPort` integer, + `website` text, + `discord` text, + `bannerUrl` text, + `tags` text DEFAULT '[]' NOT NULL, + `votifierHost` text, + `votifierPort` integer, + `votifierToken` text, + `featured` integer DEFAULT false NOT NULL, + `featuredUntil` integer, + `createdAt` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + `updatedAt` integer DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE set null +); +--> statement-breakpoint +CREATE UNIQUE INDEX `servers_slug_unique` ON `servers` (`slug`);--> statement-breakpoint +CREATE INDEX `servers_owner_idx` ON `servers` (`ownerId`);--> statement-breakpoint +CREATE INDEX `servers_featured_idx` ON `servers` (`featured`); \ No newline at end of file diff --git a/drizzle/migrations/0007_burly_madame_web.sql b/drizzle/migrations/0007_burly_madame_web.sql new file mode 100644 index 00000000..f6b67000 --- /dev/null +++ b/drizzle/migrations/0007_burly_madame_web.sql @@ -0,0 +1,2 @@ +ALTER TABLE `servers` ADD `minVersion` text;--> statement-breakpoint +ALTER TABLE `servers` ADD `maxVersion` text; \ No newline at end of file diff --git a/drizzle/migrations/0008_safe_nightshade.sql b/drizzle/migrations/0008_safe_nightshade.sql new file mode 100644 index 00000000..de55a6af --- /dev/null +++ b/drizzle/migrations/0008_safe_nightshade.sql @@ -0,0 +1 @@ +ALTER TABLE `servers` ADD `rgbPreset` text; \ No newline at end of file diff --git a/drizzle/migrations/0009_soft_krista_starr.sql b/drizzle/migrations/0009_soft_krista_starr.sql new file mode 100644 index 00000000..299997e0 --- /dev/null +++ b/drizzle/migrations/0009_soft_krista_starr.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `plugins` text; \ No newline at end of file diff --git a/drizzle/migrations/0010_stale_sumo.sql b/drizzle/migrations/0010_stale_sumo.sql new file mode 100644 index 00000000..b9aed118 --- /dev/null +++ b/drizzle/migrations/0010_stale_sumo.sql @@ -0,0 +1 @@ +ALTER TABLE `servers` ADD `plugins` text; \ No newline at end of file diff --git a/drizzle/migrations/0011_wet_stranger.sql b/drizzle/migrations/0011_wet_stranger.sql new file mode 100644 index 00000000..7c3c12d3 --- /dev/null +++ b/drizzle/migrations/0011_wet_stranger.sql @@ -0,0 +1,2 @@ +ALTER TABLE `servers` ADD `verified` integer DEFAULT false NOT NULL;--> statement-breakpoint +CREATE INDEX `servers_verified_idx` ON `servers` (`verified`); \ No newline at end of file diff --git a/drizzle/migrations/0012_black_demogoblin.sql b/drizzle/migrations/0012_black_demogoblin.sql new file mode 100644 index 00000000..74f2ff01 --- /dev/null +++ b/drizzle/migrations/0012_black_demogoblin.sql @@ -0,0 +1,8 @@ +CREATE TABLE `birdflopIpCache` ( + `id` integer PRIMARY KEY NOT NULL, + `ips` text DEFAULT '[]' NOT NULL, + `fetchedAt` integer NOT NULL +); +--> statement-breakpoint +ALTER TABLE `servers` ADD `birdflopHosted` integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE `servers` ADD `birdflopCheckedAt` integer; \ No newline at end of file diff --git a/drizzle/migrations/meta/0006_snapshot.json b/drizzle/migrations/meta/0006_snapshot.json new file mode 100644 index 00000000..85660d63 --- /dev/null +++ b/drizzle/migrations/meta/0006_snapshot.json @@ -0,0 +1,813 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f00a2455-518b-49f1-a743-83af0cc41510", + "prevId": "1fe8cfa4-5a49-4688-8103-253f339251bf", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0007_snapshot.json b/drizzle/migrations/meta/0007_snapshot.json new file mode 100644 index 00000000..fa9af144 --- /dev/null +++ b/drizzle/migrations/meta/0007_snapshot.json @@ -0,0 +1,827 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6e0e49e4-3550-4e1e-acf3-3950d2dff076", + "prevId": "f00a2455-518b-49f1-a743-83af0cc41510", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0008_snapshot.json b/drizzle/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..7e8c0782 --- /dev/null +++ b/drizzle/migrations/meta/0008_snapshot.json @@ -0,0 +1,834 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "829797e1-b572-4af1-be07-f8e14754b2f5", + "prevId": "6e0e49e4-3550-4e1e-acf3-3950d2dff076", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rgbPreset": { + "name": "rgbPreset", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0009_snapshot.json b/drizzle/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..84603a75 --- /dev/null +++ b/drizzle/migrations/meta/0009_snapshot.json @@ -0,0 +1,841 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "bfc1f761-aa89-421c-9632-fa26a95ebd35", + "prevId": "829797e1-b572-4af1-be07-f8e14754b2f5", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rgbPreset": { + "name": "rgbPreset", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0010_snapshot.json b/drizzle/migrations/meta/0010_snapshot.json new file mode 100644 index 00000000..eb702a2d --- /dev/null +++ b/drizzle/migrations/meta/0010_snapshot.json @@ -0,0 +1,848 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "12474ce3-db07-4b85-81e2-35cd18b5244b", + "prevId": "bfc1f761-aa89-421c-9632-fa26a95ebd35", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rgbPreset": { + "name": "rgbPreset", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0011_snapshot.json b/drizzle/migrations/meta/0011_snapshot.json new file mode 100644 index 00000000..a08e2155 --- /dev/null +++ b/drizzle/migrations/meta/0011_snapshot.json @@ -0,0 +1,861 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "3a6debd6-a780-40cc-a4e4-307ea2d8cc9f", + "prevId": "12474ce3-db07-4b85-81e2-35cd18b5244b", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rgbPreset": { + "name": "rgbPreset", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verified": { + "name": "verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + }, + "servers_verified_idx": { + "name": "servers_verified_idx", + "columns": ["verified"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/0012_snapshot.json b/drizzle/migrations/meta/0012_snapshot.json new file mode 100644 index 00000000..32738bcd --- /dev/null +++ b/drizzle/migrations/meta/0012_snapshot.json @@ -0,0 +1,908 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "555c97ba-8705-486d-bcaa-b2f92002cd48", + "prevId": "3a6debd6-a780-40cc-a4e4-307ea2d8cc9f", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "birdflopIpCache": { + "name": "birdflopIpCache", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "ips": { + "name": "ips", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "fetchedAt": { + "name": "fetchedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "presets": { + "name": "presets", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "colorVector": { + "name": "colorVector", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "saves": { + "name": "saves", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "presets_preset_unique": { + "name": "presets_preset_unique", + "columns": ["preset"], + "isUnique": true + } + }, + "foreignKeys": { + "presets_userId_user_id_fk": { + "name": "presets_userId_user_id_fk", + "tableFrom": "presets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "savedPresets": { + "name": "savedPresets", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "presetId": { + "name": "presetId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "savedAt": { + "name": "savedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "savedPresets_userId_user_id_fk": { + "name": "savedPresets_userId_user_id_fk", + "tableFrom": "savedPresets", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "savedPresets_presetId_presets_id_fk": { + "name": "savedPresets_presetId_presets_id_fk", + "tableFrom": "savedPresets", + "tableTo": "presets", + "columnsFrom": ["presetId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "savedPresets_userId_presetId_pk": { + "columns": ["userId", "presetId"], + "name": "savedPresets_userId_presetId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverReports": { + "name": "serverReports", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporterId": { + "name": "reporterId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved": { + "name": "resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverReports_server_idx": { + "name": "serverReports_server_idx", + "columns": ["serverId"], + "isUnique": false + }, + "serverReports_resolved_idx": { + "name": "serverReports_resolved_idx", + "columns": ["resolved"], + "isUnique": false + } + }, + "foreignKeys": { + "serverReports_serverId_servers_id_fk": { + "name": "serverReports_serverId_servers_id_fk", + "tableFrom": "serverReports", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "serverReports_reporterId_user_id_fk": { + "name": "serverReports_reporterId_user_id_fk", + "tableFrom": "serverReports", + "tableTo": "user", + "columnsFrom": ["reporterId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "serverVotes": { + "name": "serverVotes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "serverId": { + "name": "serverId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierDelivered": { + "name": "votifierDelivered", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "serverVotes_server_created_idx": { + "name": "serverVotes_server_created_idx", + "columns": ["serverId", "createdAt"], + "isUnique": false + }, + "serverVotes_username_idx": { + "name": "serverVotes_username_idx", + "columns": ["username"], + "isUnique": false + } + }, + "foreignKeys": { + "serverVotes_serverId_servers_id_fk": { + "name": "serverVotes_serverId_servers_id_fk", + "tableFrom": "serverVotes", + "tableTo": "servers", + "columnsFrom": ["serverId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "servers": { + "name": "servers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "ownerId": { + "name": "ownerId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shortDescription": { + "name": "shortDescription", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edition": { + "name": "edition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'java'" + }, + "minVersion": { + "name": "minVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "maxVersion": { + "name": "maxVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rgbPreset": { + "name": "rgbPreset", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaHost": { + "name": "javaHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "javaPort": { + "name": "javaPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockHost": { + "name": "bedrockHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrockPort": { + "name": "bedrockPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discord": { + "name": "discord", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bannerUrl": { + "name": "bannerUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "votifierHost": { + "name": "votifierHost", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierPort": { + "name": "votifierPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "votifierToken": { + "name": "votifierToken", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "featured": { + "name": "featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "featuredUntil": { + "name": "featuredUntil", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verified": { + "name": "verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "birdflopHosted": { + "name": "birdflopHosted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "birdflopCheckedAt": { + "name": "birdflopCheckedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "servers_slug_unique": { + "name": "servers_slug_unique", + "columns": ["slug"], + "isUnique": true + }, + "servers_owner_idx": { + "name": "servers_owner_idx", + "columns": ["ownerId"], + "isUnique": false + }, + "servers_featured_idx": { + "name": "servers_featured_idx", + "columns": ["featured"], + "isUnique": false + }, + "servers_verified_idx": { + "name": "servers_verified_idx", + "columns": ["verified"], + "isUnique": false + } + }, + "foreignKeys": { + "servers_ownerId_user_id_fk": { + "name": "servers_ownerId_user_id_fk", + "tableFrom": "servers", + "tableTo": "user", + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privatePresets": { + "name": "privatePresets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plugins": { + "name": "plugins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": ["username"], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index a53884e4..066b41f4 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -43,6 +43,55 @@ "when": 1770347052461, "tag": "0005_exotic_iron_patriot", "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1780222656777, + "tag": "0006_reflective_tarantula", + "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1785353982476, + "tag": "0007_burly_madame_web", + "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1785354404711, + "tag": "0008_safe_nightshade", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1785359329246, + "tag": "0009_soft_krista_starr", + "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1785359537181, + "tag": "0010_stale_sumo", + "breakpoints": true + }, + { + "idx": 11, + "version": "6", + "when": 1785466146426, + "tag": "0011_wet_stranger", + "breakpoints": true + }, + { + "idx": 12, + "version": "6", + "when": 1785594458585, + "tag": "0012_black_demogoblin", + "breakpoints": true } ] } diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 8553a48f..5839d0f4 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -3,11 +3,18 @@ import { integer, text, primaryKey, + index, } from 'drizzle-orm/sqlite-core'; import type { AdapterAccountType } from '@auth/qwik/adapters'; import { sql } from 'drizzle-orm/sql/sql'; import { rgbPreset } from '../src/util/rgb/presets'; import { Settings } from '../src/routes/layout'; +import type { PluginType } from '../src/util/plugins/ServerPlugin'; +import type { PluginsStoreType } from '../src/util/plugins/types'; +import type { + ServerEdition, + ServerTag, +} from '../src/util/serverlist/constants'; // -------------------- User -------------------- export const users = sqliteTable('user', { @@ -21,6 +28,7 @@ export const users = sqliteTable('user', { image: text('image'), privatePresets: text('privatePresets', { mode: 'json' }).$type(), settings: text('settings', { mode: 'json' }).$type(), + plugins: text('plugins', { mode: 'json' }).$type(), createdAt: integer('createdAt', { mode: 'timestamp_ms' }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), @@ -148,3 +156,142 @@ export const savedPresets = sqliteTable( }, (t) => [primaryKey({ columns: [t.userId, t.presetId] })] ); + +// -------------------- Server List -------------------- +export const servers = sqliteTable( + 'servers', + { + id: integer('id').primaryKey({ autoIncrement: true }), + // Owner who submitted the listing. Kept on delete so listings survive + // account removal, just unowned (and therefore admin-only to manage). + ownerId: text('ownerId').references(() => users.id, { + onDelete: 'set null', + }), + name: text('name').notNull(), + slug: text('slug').notNull().unique(), + description: text('description').notNull(), + shortDescription: text('shortDescription'), + edition: text('edition').$type().notNull().default('java'), + minVersion: text('minVersion'), + maxVersion: text('maxVersion'), + rgbPreset: text('rgbPreset', { mode: 'json' }).$type(), + // Connection details. Java/Bedrock hosts are independent so a "both" + // listing can point each edition at a different address. + javaHost: text('javaHost'), + javaPort: integer('javaPort'), + bedrockHost: text('bedrockHost'), + bedrockPort: integer('bedrockPort'), + website: text('website'), + discord: text('discord'), + bannerUrl: text('bannerUrl'), + tags: text('tags', { mode: 'json' }) + .$type() + .notNull() + .default(sql`'[]'`), + // NuVotifier (Votifier v2) token-protocol delivery details. + votifierHost: text('votifierHost'), + votifierPort: integer('votifierPort'), + votifierToken: text('votifierToken'), + // Sponsored / featured placement (admin-granted in v1). + featured: integer('featured', { mode: 'boolean' }).default(false).notNull(), + featuredUntil: integer('featuredUntil', { mode: 'timestamp_ms' }), + // Verified Birdflop host placement (admin-granted). + verified: integer('verified', { mode: 'boolean' }).default(false).notNull(), + plugins: text('plugins', { mode: 'json' }).$type<{ + [id: string]: PluginType; + }>(), + // Auto-detected "hosted on Birdflop" flag. Set server-side by resolving + // the listing's address against panel node IPs (see + // src/util/serverlist/birdflop.ts) โ€” never user-editable. Surfaces the + // same badge as the admin-granted `verified` flag. + birdflopHosted: integer('birdflopHosted', { mode: 'boolean' }) + .default(false) + .notNull(), + birdflopCheckedAt: integer('birdflopCheckedAt', { mode: 'timestamp_ms' }), + createdAt: integer('createdAt', { mode: 'timestamp_ms' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: integer('updatedAt', { mode: 'timestamp_ms' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + }, + (t) => [ + index('servers_owner_idx').on(t.ownerId), + index('servers_featured_idx').on(t.featured), + index('servers_verified_idx').on(t.verified), + ] +); + +export type Server = typeof servers.$inferSelect; +export type ServerInsert = typeof servers.$inferInsert; +export interface ServerWithVotes extends Server { + monthlyVotes: number; + totalVotes: number; + owner?: User | null; +} + +// -------------------- Server Votes -------------------- +export const serverVotes = sqliteTable( + 'serverVotes', + { + id: integer('id').primaryKey({ autoIncrement: true }), + serverId: integer('serverId') + .notNull() + .references(() => servers.id, { onDelete: 'cascade' }), + // Minecraft username supplied at vote time (voting requires no login). + username: text('username').notNull(), + ip: text('ip'), + votifierDelivered: integer('votifierDelivered', { mode: 'boolean' }) + .default(false) + .notNull(), + createdAt: integer('createdAt', { mode: 'timestamp_ms' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + }, + (t) => [ + index('serverVotes_server_created_idx').on(t.serverId, t.createdAt), + index('serverVotes_username_idx').on(t.username), + ] +); + +export type ServerVote = typeof serverVotes.$inferSelect; + +// -------------------- Server Reports -------------------- +export const serverReports = sqliteTable( + 'serverReports', + { + id: integer('id').primaryKey({ autoIncrement: true }), + serverId: integer('serverId') + .notNull() + .references(() => servers.id, { onDelete: 'cascade' }), + reason: text('reason').notNull(), + details: text('details'), + reporterId: text('reporterId').references(() => users.id, { + onDelete: 'set null', + }), + ip: text('ip'), + resolved: integer('resolved', { mode: 'boolean' }).default(false).notNull(), + createdAt: integer('createdAt', { mode: 'timestamp_ms' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + }, + (t) => [ + index('serverReports_server_idx').on(t.serverId), + index('serverReports_resolved_idx').on(t.resolved), + ] +); + +export type ServerReport = typeof serverReports.$inferSelect; + +// -------------------- Birdflop node IP cache -------------------- +// Single-row cache (id = 1) of the panel's public node/allocation IPs so +// Birdflop-hosted detection doesn't hit the panel API on every check. +// Server-side only; never returned by any loader or endpoint. +export const birdflopIpCache = sqliteTable('birdflopIpCache', { + id: integer('id').primaryKey(), + ips: text('ips', { mode: 'json' }) + .$type() + .notNull() + .default(sql`'[]'`), + fetchedAt: integer('fetchedAt', { mode: 'timestamp_ms' }).notNull(), +}); diff --git a/package.json b/package.json index ae1f0f59..aef80d79 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "type": "module", "scripts": { "build": "pnpm build.pkg && wrangler types && qwik build", - "build.pkg": "cd packages/rgbirdflop && pnpm run build && cd ../analyze && pnpm run build", + "build.pkg": "cd packages/rgbirdflop && pnpm run build && cd ../analyze && pnpm run build && cd ../mc-status && pnpm run build", "build.client": "vp build", "build.preview": "vp build --ssr src/entry.preview.tsx", "build.server": "qwik check-client src dist && vp build -c adapters/cloudflare-workers/vite.config.ts", @@ -62,23 +62,24 @@ "@auth/drizzle-adapter": "^1.11.3", "@auth/qwik": "0.9.3", "@birdflop/analyze": "workspace:*", + "@birdflop/mc-status": "workspace:*", "@birdflop/rgbirdflop": "workspace:*", "@builder.io/qwik-city": "^1.20.0", "@luminescent/icons-qwik": "^0.1.1", "@luminescent/ui": "^7.2.0-1", - "@luminescent/ui-qwik": "7.0.0-25", + "@luminescent/ui-qwik": "7.0.0-28", "@qwik.dev/core": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8785", "@qwik.dev/partytown": "^0.14.0", "@qwik.dev/router": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@8785", - "@shikijs/colorized-brackets": "^4.3.1", - "@shikijs/engine-javascript": "^4.3.1", - "@shikijs/rehype": "^4.3.1", - "@shikijs/transformers": "^4.3.1", - "@shikijs/types": "^4.3.1", + "@shikijs/colorized-brackets": "^4.4.1", + "@shikijs/engine-javascript": "^4.4.1", + "@shikijs/rehype": "^4.4.1", + "@shikijs/transformers": "^4.4.1", + "@shikijs/types": "^4.4.1", "@tailwindcss/vite": "^4.3.3", - "@types/better-sqlite3": "^7.6.13", + "@types/better-sqlite3": "^9.6.0", "@types/node": "26.1.2", - "@types/three": "^0.185.1", + "@types/three": "^0.185.3", "better-sqlite3": "^13.0.2", "chart.js": "^4.5.1", "drizzle-kit": "^0.31.10", @@ -90,7 +91,7 @@ "lucide-icons-qwik": "1.25.0-qwik-v2", "oxlint-plugin-qwik": "^0.0.9", "qwik-speak": "^0.24.0", - "shiki": "^4.3.1", + "shiki": "^4.4.1", "simple-icons-qwik": "16.27.0-qwik-v2", "tailwindcss": "^4.3.3", "three": "^0.185.1", @@ -103,7 +104,7 @@ "vite": "catalog:", "vite-plus": "catalog:", "vitest": "catalog:", - "wrangler": "^4.115.0", + "wrangler": "^4.118.0", "yaml": "^2.9.0" } } diff --git a/packages/mc-status/.gitignore b/packages/mc-status/.gitignore new file mode 100644 index 00000000..8a696dd4 --- /dev/null +++ b/packages/mc-status/.gitignore @@ -0,0 +1,15 @@ +# Build +/dist +/lib +/lib-types + +# Development +node_modules +*.local + +# Cache +.cache +tsconfig.tsbuildinfo + +# Logs +*.log diff --git a/packages/mc-status/LICENSE b/packages/mc-status/LICENSE new file mode 100644 index 00000000..2e98756a --- /dev/null +++ b/packages/mc-status/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Birdflop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/mc-status/package.json b/packages/mc-status/package.json new file mode 100644 index 00000000..6a427bab --- /dev/null +++ b/packages/mc-status/package.json @@ -0,0 +1,33 @@ +{ + "name": "@birdflop/mc-status", + "version": "1.0.0", + "description": "Minecraft Server Status client for Java & Bedrock with full 1.16+ RGB hex color and MOTD component support.", + "main": "./lib/index.js", + "types": "./lib-types/index.d.ts", + "exports": { + ".": { + "import": "./lib/index.js", + "require": "./lib/index.js", + "types": "./lib-types/index.d.ts" + } + }, + "files": [ + "lib", + "lib-types" + ], + "engines": { + "node": ">=24.13.0" + }, + "license": "MIT", + "private": false, + "type": "module", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run" + }, + "devDependencies": { + "@types/node": "^25.0.3", + "typescript": "7.0.2" + } +} diff --git a/packages/mc-status/src/__tests__/status.test.ts b/packages/mc-status/src/__tests__/status.test.ts new file mode 100644 index 00000000..e64cf338 --- /dev/null +++ b/packages/mc-status/src/__tests__/status.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest'; +import { readVarInt, writeVarInt } from '../protocol/varint.js'; +import { parseMotd } from '../motd/parser.js'; +import { shadowColor } from '../motd/colors.js'; + +describe('@birdflop/mc-status', () => { + describe('VarInt serialization', () => { + it('should encode and decode 0', () => { + const buf = writeVarInt(0); + expect(readVarInt(buf)).toEqual({ value: 0, bytesRead: 1 }); + }); + + it('should encode and decode 255', () => { + const buf = writeVarInt(255); + expect(readVarInt(buf)).toEqual({ value: 255, bytesRead: 2 }); + }); + + it('should encode and decode protocol version 767', () => { + const buf = writeVarInt(767); + expect(readVarInt(buf)).toEqual({ value: 767, bytesRead: 2 }); + }); + }); + + describe('MOTD parsing with Hex Colors', () => { + it('should parse 1.16+ JSON Chat Component with hex color', () => { + const component = { + text: '', + extra: [ + { text: 'Hello ', color: '#FF54A1', bold: true }, + { text: 'World', color: 'green' }, + ], + }; + + const result = parseMotd(component); + expect(result.clean).toBe('Hello World'); + expect(result.runs[0][0].style.color).toBe('#FF54A1'); + expect(result.runs[0][0].style.bold).toBe(true); + expect(result.runs[0][1].style.color).toBe('#55FF55'); + expect(result.html).toContain('color: #FF54A1'); + }); + + it('should parse Bungeecord spread hex format (ยงxยงfยงfยง5ยง4ยงaยง1)', () => { + const raw = 'ยงxยงfยงfยง5ยง4ยงaยง1Gradient Text'; + const result = parseMotd(raw); + expect(result.clean).toBe('Gradient Text'); + expect(result.runs[0][0].style.color).toBe('#FF54A1'); + expect(result.html).toContain('color: #FF54A1'); + }); + + it('should parse ampersand hash hex (&#ff54a1)', () => { + const raw = '&#ff54a1Hex Text'; + const result = parseMotd(raw); + expect(result.clean).toBe('Hex Text'); + expect(result.runs[0][0].style.color).toBe('#FF54A1'); + }); + + it('should parse MiniMessage format (<#ff54a1>)', () => { + const raw = '<#ff54a1>MiniMessage Hex'; + const result = parseMotd(raw); + expect(result.clean).toBe('MiniMessage Hex'); + expect(result.runs[0][0].style.color).toBe('#FF54A1'); + }); + + it('should preserve component hex color when text contains 1.8 legacy fallback code', () => { + const component = { + color: '#FF54A1', + text: 'ยงaGradient Text', + }; + const result = parseMotd(component); + expect(result.clean).toBe('Gradient Text'); + expect(result.runs[0][0].style.color).toBe('#FF54A1'); + }); + }); + + describe('Shadow calculation', () => { + it('should calculate 25% brightness shadow color', () => { + expect(shadowColor('#FF54A1')).toBe('#401528'); + expect(shadowColor('#55FF55')).toBe('#154015'); + expect(shadowColor('#FFFFFF')).toBe('#404040'); + }); + }); +}); diff --git a/packages/mc-status/src/index.ts b/packages/mc-status/src/index.ts new file mode 100644 index 00000000..d6b815f5 --- /dev/null +++ b/packages/mc-status/src/index.ts @@ -0,0 +1,43 @@ +import { pingJava } from './protocol/java.js'; +import { pingBedrock } from './protocol/bedrock.js'; +import type { PingOptions, StatusResult } from './types.js'; + +export { pingJava } from './protocol/java.js'; +export { pingBedrock } from './protocol/bedrock.js'; +export { + parseMotd, + parseMotdLine, + normalizeMotdText, + parseChatComponent, + motdRunsToHtml, + motdRunsToClean, + motdRunsToRaw, + defaultStyle, +} from './motd/parser.js'; +export { + MC_COLORS, + COLOR_BY_CODE, + NAMED_COLORS, + normalizeHexColor, + shadowColor, +} from './motd/colors.js'; +export * from './types.js'; + +/** + * Ping a Minecraft server (attempts Java SLP first, then Bedrock UDP if Java fails). + */ +export async function ping( + host: string, + port?: number, + options: PingOptions = {} +): Promise { + try { + return await pingJava(host, port ?? 25565, options); + } catch (javaError) { + try { + return await pingBedrock(host, port ?? 19132, options); + } catch { + throw javaError; + } + } +} diff --git a/packages/mc-status/src/motd/colors.ts b/packages/mc-status/src/motd/colors.ts new file mode 100644 index 00000000..eeb41bd9 --- /dev/null +++ b/packages/mc-status/src/motd/colors.ts @@ -0,0 +1,81 @@ +export interface MCColor { + code: string; + name: string; + hex: string; +} + +export const MC_COLORS: MCColor[] = [ + { code: '0', name: 'black', hex: '#000000' }, + { code: '1', name: 'dark_blue', hex: '#0000AA' }, + { code: '2', name: 'dark_green', hex: '#00AA00' }, + { code: '3', name: 'dark_aqua', hex: '#00AAAA' }, + { code: '4', name: 'dark_red', hex: '#AA0000' }, + { code: '5', name: 'dark_purple', hex: '#AA00AA' }, + { code: '6', name: 'gold', hex: '#FFAA00' }, + { code: '7', name: 'gray', hex: '#AAAAAA' }, + { code: '8', name: 'dark_gray', hex: '#555555' }, + { code: '9', name: 'blue', hex: '#5555FF' }, + { code: 'a', name: 'green', hex: '#55FF55' }, + { code: 'b', name: 'aqua', hex: '#55FFFF' }, + { code: 'c', name: 'red', hex: '#FF5555' }, + { code: 'd', name: 'light_purple', hex: '#FF55FF' }, + { code: 'e', name: 'yellow', hex: '#FFFF55' }, + { code: 'f', name: 'white', hex: '#FFFFFF' }, +]; + +export const COLOR_BY_CODE: Record = Object.fromEntries( + MC_COLORS.map((c) => [c.code, c.hex]) +); + +export const NAMED_COLORS: Record = Object.fromEntries([ + ...MC_COLORS.map((c) => [c.name, c.hex]), + ...MC_COLORS.map((c) => [c.name.replace('_', ''), c.hex]), + ['lightpurple', '#FF55FF'], + ['darkblue', '#0000AA'], + ['darkgreen', '#00AA00'], + ['darkaqua', '#00AAAA'], + ['darkred', '#AA0000'], + ['darkpurple', '#AA00AA'], + ['darkgray', '#555555'], + ['reset', '#FFFFFF'], +]); + +export function normalizeHexColor(input: string): string | null { + if (!input) return null; + const trimmed = input.trim().toLowerCase(); + + if (trimmed in NAMED_COLORS) { + return NAMED_COLORS[trimmed]; + } + + const cleanHex = trimmed.replace(/^#/, ''); + if (/^[0-9a-f]{6}$/.test(cleanHex)) { + return `#${cleanHex.toUpperCase()}`; + } + if (/^[0-9a-f]{3}$/.test(cleanHex)) { + const [r, g, b] = cleanHex.split(''); + return `#${r}${r}${g}${g}${b}${b}`.toUpperCase(); + } + + return null; +} + +export function shadowColor(hex: string): string { + if (!hex || !hex.startsWith('#') || hex.length !== 7) return '#3f3f3f'; + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return '#3f3f3f'; + + const sr = Math.round(r * 0.25) + .toString(16) + .padStart(2, '0'); + const sg = Math.round(g * 0.25) + .toString(16) + .padStart(2, '0'); + const sb = Math.round(b * 0.25) + .toString(16) + .padStart(2, '0'); + + return `#${sr}${sg}${sb}`.toUpperCase(); +} diff --git a/packages/mc-status/src/motd/parser.ts b/packages/mc-status/src/motd/parser.ts new file mode 100644 index 00000000..330c4233 --- /dev/null +++ b/packages/mc-status/src/motd/parser.ts @@ -0,0 +1,357 @@ +import type { + ChatComponent, + MotdResult, + MotdRun, + MotdStyle, +} from '../types.js'; +import { + COLOR_BY_CODE, + MC_COLORS, + normalizeHexColor, + shadowColor, +} from './colors.js'; + +const LEGACY_HEX_SET = new Set(MC_COLORS.map((c) => c.hex.toUpperCase())); + +export function isCustomHexColor(color: string): boolean { + if (!color || !color.startsWith('#')) return false; + const upper = color.toUpperCase(); + return upper.length === 7 && !LEGACY_HEX_SET.has(upper); +} + +export function defaultStyle(): MotdStyle { + return { + color: '#FFFFFF', + bold: false, + italic: false, + underline: false, + strikethrough: false, + obfuscated: false, + }; +} + +export function normalizeMotdText(input: string): string { + if (!input) return ''; + + let text = input.replace(/\\u00a7/gi, 'ยง'); + + // 1. Convert HTML tags to ยง formatting codes + text = text + .replace(//gi, '\n') + .replace( + /]*style="[^"]*color:\s*#([0-9a-fA-F]{6})[^"]*"[^>]*>/gi, + 'ยง#$1' + ) + .replace( + /]*style="[^"]*color:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)[^"]*"[^>]*>/gi, + (_, r, g, b) => { + const hex = [r, g, b] + .map((x) => + Math.min(255, Math.max(0, parseInt(x, 10))) + .toString(16) + .padStart(2, '0') + ) + .join(''); + return `ยง#${hex}`; + } + ) + .replace( + /]*class="[^"]*motd-color-([0-9a-fA-F]{6})[^"]*"[^>]*>/gi, + 'ยง#$1' + ) + .replace(/]*style="[^"]*font-weight:\s*bold[^"]*"[^>]*>/gi, 'ยงl') + .replace(/]*style="[^"]*font-style:\s*italic[^"]*"[^>]*>/gi, 'ยงo') + .replace(/<\/span>/gi, 'ยงr') + .replace(/<[^>]+>/g, (tag) => { + const hexMatch = tag.match(/^<(?:color:|c:)?#([0-9a-fA-F]{6})>$/i); + if (hexMatch) return `ยง#${hexMatch[1]}`; + + const miniMsgTags: Record = { + '': 'ยงr', + '': 'ยงr', + '': 'ยงr', + '': 'ยงl', + '': 'ยงl', + '': 'ยงo', + '': 'ยงo', + '': 'ยงn', + '': 'ยงn', + '': 'ยงm', + '': 'ยงm', + '': 'ยงk', + '': 'ยงk', + }; + const lower = tag.toLowerCase(); + if (lower in miniMsgTags) return miniMsgTags[lower]; + return ''; + }); + + // 2. Spread hex: &x&1&2&3&4&5&6 or ยงxยง1ยง2ยง3ยง4ยง5ยง6 -> ยง#123456 + text = text.replace( + /[&ยง]x[&ยง]([0-9a-fA-F])[&ยง]([0-9a-fA-F])[&ยง]([0-9a-fA-F])[&ยง]([0-9a-fA-F])[&ยง]([0-9a-fA-F])[&ยง]([0-9a-fA-F])/gi, + 'ยง#$1$2$3$4$5$6' + ); + + // 3. Compact x hex: &x123456 or ยงx123456 -> ยง#123456 + text = text.replace(/[&ยง]x([0-9a-fA-F]{6})/gi, 'ยง#$1'); + + // 4. Ampersand hash hex: 𞉀 -> ยง#123456 + text = text.replace(/&#([0-9a-fA-F]{6})/gi, 'ยง#$1'); + + return text; +} + +export function parseMotdLine( + rawLine: string, + initialStyle: MotdStyle = defaultStyle(), + hasExplicitColor = false +): MotdRun[] { + const line = normalizeMotdText(rawLine); + const runs: MotdRun[] = []; + let style = { ...initialStyle }; + let isExplicit = hasExplicitColor || isCustomHexColor(initialStyle.color); + let buffer = ''; + + const flush = () => { + if (buffer) { + runs.push({ text: buffer, style: { ...style } }); + buffer = ''; + } + }; + + const chars = Array.from(line); + for (let i = 0; i < chars.length; i++) { + const c = chars[i]; + const isCodeChar = c === '&' || c === 'ยง'; + if (!isCodeChar || i + 1 >= chars.length) { + buffer += c; + continue; + } + + const next = chars[i + 1]; + + // Hex: &#RRGGBB + if ( + next === '#' && + /^[0-9a-fA-F]{6}$/.test(chars.slice(i + 2, i + 8).join('')) + ) { + flush(); + const hexColor = + '#' + + chars + .slice(i + 2, i + 8) + .join('') + .toUpperCase(); + style = { + ...style, + color: hexColor, + }; + isExplicit = true; + i += 7; + continue; + } + + const lower = next.toLowerCase(); + if (lower in COLOR_BY_CODE) { + flush(); + // If we already have an explicit hex color, ignore 1.8 legacy fallback codes (ยงc, ยงa, etc.) + if (!isExplicit) { + style = { ...style, color: COLOR_BY_CODE[lower] }; + } + i++; + continue; + } + + switch (lower) { + case 'l': + flush(); + style.bold = true; + i++; + continue; + case 'o': + flush(); + style.italic = true; + i++; + continue; + case 'n': + flush(); + style.underline = true; + i++; + continue; + case 'm': + flush(); + style.strikethrough = true; + i++; + continue; + case 'k': + flush(); + style.obfuscated = true; + i++; + continue; + case 'r': + flush(); + style = { ...initialStyle }; + isExplicit = hasExplicitColor || isCustomHexColor(initialStyle.color); + i++; + continue; + } + + buffer += c; + } + + flush(); + return runs; +} + +export function parseChatComponent( + component: string | ChatComponent | (string | ChatComponent)[] +): MotdRun[][] { + const lines: MotdRun[][] = [[]]; + + function processComponent( + comp: string | ChatComponent, + parentStyle: MotdStyle + ) { + if (typeof comp === 'string') { + const parsedLines = comp.split(/\r?\n/); + parsedLines.forEach((lineText, idx) => { + if (idx > 0) lines.push([]); + if (lineText) { + const lineRuns = parseMotdLine(lineText, parentStyle); + const currentLine = lines[lines.length - 1]; + lineRuns.forEach((run) => { + currentLine.push(run); + }); + } + }); + return; + } + + if (!comp) return; + + const hasCompColor = Boolean(comp.color); + const resolvedColor = comp.color + ? (normalizeHexColor(comp.color) ?? parentStyle.color) + : parentStyle.color; + + const currentStyle: MotdStyle = { + color: resolvedColor, + bold: comp.bold ?? parentStyle.bold, + italic: comp.italic ?? parentStyle.italic, + underline: comp.underlined ?? parentStyle.underline, + strikethrough: comp.strikethrough ?? parentStyle.strikethrough, + obfuscated: comp.obfuscated ?? parentStyle.obfuscated, + }; + + if (comp.text) { + const textLines = comp.text.split(/\r?\n/); + textLines.forEach((lineText, idx) => { + if (idx > 0) lines.push([]); + if (lineText) { + const lineRuns = parseMotdLine( + lineText, + currentStyle, + hasCompColor || isCustomHexColor(currentStyle.color) + ); + const currentLine = lines[lines.length - 1]; + lineRuns.forEach((run) => { + currentLine.push(run); + }); + } + }); + } + + if (Array.isArray(comp.extra)) { + comp.extra.forEach((subComp) => processComponent(subComp, currentStyle)); + } + } + + if (Array.isArray(component)) { + component.forEach((item) => processComponent(item, defaultStyle())); + } else { + processComponent(component, defaultStyle()); + } + + return lines.filter((line) => line.length > 0); +} + +export function motdRunsToHtml(lines: MotdRun[][]): string { + return lines + .map((line) => + line + .map((run) => { + const styles: string[] = [`color: ${run.style.color}`]; + if (run.style.bold) styles.push('font-weight: bold'); + if (run.style.italic) styles.push('font-style: italic'); + + const decos: string[] = []; + if (run.style.underline) decos.push('underline'); + if (run.style.strikethrough) decos.push('line-through'); + if (decos.length > 0) + styles.push(`text-decoration: ${decos.join(' ')}`); + + const shadow = shadowColor(run.style.color); + styles.push(`text-shadow: 2px 2px 0 ${shadow}`); + + const escapedText = run.text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + + const className = run.style.obfuscated + ? ' class="motd-obfuscated"' + : ''; + + return `${escapedText}`; + }) + .join('') + ) + .join('\n'); +} + +export function motdRunsToRaw(lines: MotdRun[][]): string { + return lines + .map((line) => + line + .map((run) => { + let prefix = `ยง#${run.style.color.replace('#', '')}`; + if (run.style.bold) prefix += 'ยงl'; + if (run.style.italic) prefix += 'ยงo'; + if (run.style.underline) prefix += 'ยงn'; + if (run.style.strikethrough) prefix += 'ยงm'; + if (run.style.obfuscated) prefix += 'ยงk'; + return prefix + run.text; + }) + .join('') + ) + .join('\n'); +} + +export function motdRunsToClean(lines: MotdRun[][]): string { + return lines.map((line) => line.map((run) => run.text).join('')).join('\n'); +} + +export function parseMotd(input: unknown): MotdResult { + let runs: MotdRun[][]; + + if (!input) { + runs = [[]]; + } else if (typeof input === 'object' && input !== null) { + runs = parseChatComponent(input); + } else if (Array.isArray(input)) { + runs = parseChatComponent(input as (string | ChatComponent)[]); + } else if (typeof input === 'string') { + const rawLines = input.split(/\r?\n/); + runs = rawLines.map((l) => parseMotdLine(l)); + } else { + runs = [[]]; + } + + return { + raw: motdRunsToRaw(runs), + clean: motdRunsToClean(runs), + html: motdRunsToHtml(runs), + runs, + }; +} diff --git a/packages/mc-status/src/protocol/bedrock.ts b/packages/mc-status/src/protocol/bedrock.ts new file mode 100644 index 00000000..89f61ad7 --- /dev/null +++ b/packages/mc-status/src/protocol/bedrock.ts @@ -0,0 +1,98 @@ +import dgram from 'node:dgram'; +import type { BedrockStatusResult, PingOptions } from '../types.js'; +import { parseMotd } from '../motd/parser.js'; + +const DEFAULT_BEDROCK_PORT = 19132; +const RAKNET_MAGIC = Buffer.from('0000ff00fe89a950b0b643388017a554', 'hex'); + +export async function pingBedrock( + host: string, + port: number = DEFAULT_BEDROCK_PORT, + options: PingOptions = {} +): Promise { + const timeout = options.timeout ?? 5000; + const startTime = Date.now(); + + return new Promise((resolve, reject) => { + const socket = dgram.createSocket('udp4'); + let isSettled = false; + + const cleanup = () => { + try { + socket.close(); + } catch { + // Ignore socket close errors + } + clearTimeout(timer); + }; + + const fail = (err: Error) => { + if (isSettled) return; + isSettled = true; + cleanup(); + reject(err); + }; + + const timer = setTimeout(() => { + fail( + new Error(`UDP ping timed out after ${timeout}ms for ${host}:${port}`) + ); + }, timeout); + + socket.on('error', (err: Error) => { + fail(err); + }); + + socket.on('message', (msg: Buffer) => { + try { + if (msg.length < 35 || msg[0] !== 0x1c) return; + + const latency = Date.now() - startTime; + const strLen = msg.readUInt16BE(33); + const payloadStr = msg.toString('utf8', 35, 35 + strLen); + + const parts = payloadStr.split(';'); + + const line1 = parts[1] ?? ''; + const line2 = parts[7] ?? ''; + const rawMotd = line2 ? `${line1}\n${line2}` : line1; + + const motd = parseMotd(rawMotd); + + const result: BedrockStatusResult = { + edition: 'bedrock', + online: true, + latency, + version: { + name: parts[3] ?? 'Bedrock', + protocol: Number(parts[2] ?? 0), + }, + players: { + online: Number(parts[4] ?? 0), + max: Number(parts[5] ?? 0), + }, + motd, + serverId: parts[6], + gameMode: parts[8], + }; + + isSettled = true; + cleanup(); + resolve(result); + } catch (err) { + fail(err instanceof Error ? err : new Error(String(err))); + } + }); + + // Send RakNet Unconnected Ping packet + const pingBuf = Buffer.alloc(33); + pingBuf.writeUInt8(0x01, 0); // ID_UNCONNECTED_PING + pingBuf.writeBigInt64BE(BigInt(startTime), 1); + RAKNET_MAGIC.copy(pingBuf, 9); + pingBuf.writeBigInt64BE(BigInt(0), 25); + + socket.send(pingBuf, port, host, (err: Error | null) => { + if (err) fail(err); + }); + }); +} diff --git a/packages/mc-status/src/protocol/java.ts b/packages/mc-status/src/protocol/java.ts new file mode 100644 index 00000000..e992cb9c --- /dev/null +++ b/packages/mc-status/src/protocol/java.ts @@ -0,0 +1,163 @@ +import dns from 'node:dns/promises'; +import net from 'node:net'; +import type { JavaStatusResult, PingOptions } from '../types.js'; +import { readString, readVarInt, writeString, writeVarInt } from './varint.js'; +import { parseMotd } from '../motd/parser.js'; + +const DEFAULT_JAVA_PORT = 25565; +const DEFAULT_PROTOCOL_VERSION = 767; // 1.20.6+ for 1.16+ RGB Hex MOTD + +export async function pingJava( + host: string, + port: number = DEFAULT_JAVA_PORT, + options: PingOptions = {} +): Promise { + const timeout = options.timeout ?? 5000; + const protocolVersion = options.protocolVersion ?? DEFAULT_PROTOCOL_VERSION; + const startTime = Date.now(); + + let targetHost = host; + let targetPort = port; + + if (port === DEFAULT_JAVA_PORT) { + try { + const srvs = await dns.resolveSrv(`_minecraft._tcp.${host}`); + if (srvs && srvs.length > 0 && srvs[0].name) { + targetHost = srvs[0].name; + targetPort = srvs[0].port; + } + } catch { + // Ignore SRV lookup errors + } + } + + return new Promise((resolve, reject) => { + const socket = new net.Socket(); + let isSettled = false; + let receivedBuffer = Buffer.alloc(0); + + const cleanup = () => { + socket.destroy(); + clearTimeout(timer); + }; + + const fail = (err: Error) => { + if (isSettled) return; + isSettled = true; + cleanup(); + reject(err); + }; + + const timer = setTimeout(() => { + fail( + new Error(`TCP ping timed out after ${timeout}ms for ${host}:${port}`) + ); + }, timeout); + + socket.on('error', (err: Error) => { + fail(err); + }); + + socket.connect(targetPort, targetHost, () => { + try { + // Handshake packet (ID 0x00) + const packetId = writeVarInt(0x00); + const pVersion = writeVarInt(protocolVersion); + const pHost = writeString(host); + const pPort = Buffer.alloc(2); + pPort.writeUInt16BE(port, 0); + const pNextState = writeVarInt(1); // 1 = Status + + const handshakePayload = Buffer.concat([ + packetId, + pVersion, + pHost, + pPort, + pNextState, + ]); + const handshakePacket = Buffer.concat([ + writeVarInt(handshakePayload.length), + handshakePayload, + ]); + + // Status Request packet (ID 0x00) + const statusRequestPayload = writeVarInt(0x00); + const statusRequestPacket = Buffer.concat([ + writeVarInt(statusRequestPayload.length), + statusRequestPayload, + ]); + + socket.write(Buffer.concat([handshakePacket, statusRequestPacket])); + } catch (err) { + fail(err instanceof Error ? err : new Error(String(err))); + } + }); + + socket.on('data', (chunk: Buffer) => { + receivedBuffer = Buffer.concat([receivedBuffer, chunk]); + + try { + if (receivedBuffer.length < 2) return; + + let offset = 0; + const { value: packetLength, bytesRead: lenBytes } = readVarInt( + receivedBuffer, + offset + ); + offset += lenBytes; + + if (receivedBuffer.length < lenBytes + packetLength) { + return; // Wait for full packet + } + + const { value: packetId, bytesRead: idBytes } = readVarInt( + receivedBuffer, + offset + ); + offset += idBytes; + + if (packetId !== 0x00) { + fail(new Error(`Unexpected packet ID: 0x${packetId.toString(16)}`)); + return; + } + + const { value: jsonString } = readString(receivedBuffer, offset); + const latency = Date.now() - startTime; + + const rawJson = JSON.parse(jsonString) as Record; + + const motd = parseMotd(rawJson.description); + + const playersObj = (rawJson.players ?? {}) as Record; + const versionObj = (rawJson.version ?? {}) as Record; + + const result: JavaStatusResult = { + edition: 'java', + online: true, + latency, + version: { + name: + typeof versionObj.name === 'string' ? versionObj.name : 'Unknown', + protocol: Number(versionObj.protocol ?? 0), + }, + players: { + online: Number(playersObj.online ?? 0), + max: Number(playersObj.max ?? 0), + sample: Array.isArray(playersObj.sample) + ? (playersObj.sample as Array<{ name: string; id: string }>) + : undefined, + }, + motd, + favicon: typeof rawJson.favicon === 'string' ? rawJson.favicon : null, + rawResponse: rawJson, + }; + + isSettled = true; + cleanup(); + resolve(result); + } catch (err) { + fail(err instanceof Error ? err : new Error(String(err))); + } + }); + }); +} diff --git a/packages/mc-status/src/protocol/varint.ts b/packages/mc-status/src/protocol/varint.ts new file mode 100644 index 00000000..38b8b826 --- /dev/null +++ b/packages/mc-status/src/protocol/varint.ts @@ -0,0 +1,59 @@ +export function writeVarInt(val: number): Buffer { + let value = val; + const bytes: number[] = []; + while (true) { + if ((value & ~0x7f) === 0) { + bytes.push(value); + break; + } + bytes.push((value & 0x7f) | 0x80); + value >>>= 7; + } + return Buffer.from(bytes); +} + +export function readVarInt( + buffer: Buffer, + offset = 0 +): { value: number; bytesRead: number } { + let value = 0; + let bytesRead = 0; + let currentByte = 0; + + while (true) { + if (offset + bytesRead >= buffer.length) { + throw new Error('Buffer underflow while reading VarInt'); + } + currentByte = buffer[offset + bytesRead]; + value |= (currentByte & 0x7f) << (bytesRead * 7); + bytesRead++; + if (bytesRead > 5) { + throw new Error('VarInt is too big'); + } + if ((currentByte & 0x80) === 0) { + break; + } + } + + return { value, bytesRead }; +} + +export function writeString(str: string): Buffer { + const strBuffer = Buffer.from(str, 'utf8'); + const lenBuffer = writeVarInt(strBuffer.length); + return Buffer.concat([lenBuffer, strBuffer]); +} + +export function readString( + buffer: Buffer, + offset = 0 +): { value: string; bytesRead: number } { + const { value: length, bytesRead: lenBytes } = readVarInt(buffer, offset); + const start = offset + lenBytes; + const end = start + length; + if (end > buffer.length) { + throw new Error('Buffer underflow while reading String'); + } + const value = buffer.toString('utf8', start, end); + return { value, bytesRead: lenBytes + length }; +} diff --git a/packages/mc-status/src/types.ts b/packages/mc-status/src/types.ts new file mode 100644 index 00000000..4b891e0b --- /dev/null +++ b/packages/mc-status/src/types.ts @@ -0,0 +1,97 @@ +export interface MotdStyle { + color: string; // e.g. '#FF54A1' + bold: boolean; + italic: boolean; + underline: boolean; + strikethrough: boolean; + obfuscated: boolean; +} + +export interface MotdRun { + text: string; + style: MotdStyle; +} + +export interface MotdResult { + raw: string; + clean: string; + html: string; + runs: MotdRun[][]; // Lines -> Runs +} + +export interface ServerMotd { + raw: string; + clean: string; + html?: string | null; +} + +export interface ServerStatus { + online: boolean; + players: { online: number; max: number }; + version: string | null; + motd: ServerMotd | null; + icon: string | null; + edition: 'java' | 'bedrock'; + host: string; + port: number | null; +} + +export interface ChatComponent { + text?: string; + color?: string; + bold?: boolean; + italic?: boolean; + underlined?: boolean; + strikethrough?: boolean; + obfuscated?: boolean; + extra?: ChatComponent[]; + translate?: string; + with?: (string | ChatComponent)[]; +} + +export interface PlayerSample { + name: string; + id: string; +} + +export interface JavaStatusResult { + edition: 'java'; + online: boolean; + latency: number; // ms + version: { + name: string; + protocol: number; + }; + players: { + online: number; + max: number; + sample?: PlayerSample[]; + }; + motd: MotdResult; + favicon: string | null; // data:image/png;base64,... + rawResponse?: Record; +} + +export interface BedrockStatusResult { + edition: 'bedrock'; + online: boolean; + latency: number; // ms + version: { + name: string; + protocol: number; + }; + players: { + online: number; + max: number; + }; + motd: MotdResult; + gameMode?: string; + serverId?: string; +} + +export type StatusResult = JavaStatusResult | BedrockStatusResult; + +export interface PingOptions { + timeout?: number; // default 5000ms + protocolVersion?: number; // default 767 (1.20.6+) for Java +} diff --git a/packages/mc-status/tsconfig.json b/packages/mc-status/tsconfig.json new file mode 100644 index 00000000..3fa2b3e5 --- /dev/null +++ b/packages/mc-status/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowJs": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["node"], + "declaration": true, + "declarationDir": "lib-types", + "outDir": "lib", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc6a1fbd..0dd3af1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -228,6 +228,9 @@ importers: '@birdflop/analyze': specifier: workspace:* version: link:packages/analyze + '@birdflop/mc-status': + specifier: workspace:* + version: link:packages/mc-status '@birdflop/rgbirdflop': specifier: workspace:* version: link:packages/rgbirdflop @@ -241,8 +244,8 @@ importers: specifier: ^7.2.0-1 version: 7.2.0-1 '@luminescent/ui-qwik': - specifier: 7.0.0-25 - version: 7.0.0-25(@luminescent/ui@7.2.0-1) + specifier: 7.0.0-28 + version: 7.0.0-28(@luminescent/ui@7.2.0-1) '@qwik.dev/core': specifier: https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8785 version: https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8785(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.10) @@ -253,32 +256,32 @@ importers: specifier: https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@8785 version: https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@8785(@qwik.dev/core@https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8785(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0))(vitest@4.1.10))(@types/node@26.1.2)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0))(rollup@4.62.3)(typescript@7.0.2) '@shikijs/colorized-brackets': - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 '@shikijs/engine-javascript': - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 '@shikijs/rehype': - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 '@shikijs/transformers': - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 '@shikijs/types': - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 '@tailwindcss/vite': specifier: ^4.3.3 version: 4.3.3(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0)) '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 + specifier: ^9.6.0 + version: 9.6.0 '@types/node': specifier: 26.1.2 version: 26.1.2 '@types/three': - specifier: ^0.185.1 - version: 0.185.1 + specifier: ^0.185.3 + version: 0.185.3 better-sqlite3: specifier: ^13.0.2 version: 13.0.2 @@ -290,7 +293,7 @@ importers: version: 0.31.10 drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@13.0.2) + version: 0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.2) gifuct-js: specifier: ^2.1.2 version: 2.1.2 @@ -313,8 +316,8 @@ importers: specifier: ^0.24.0 version: 0.24.0(@builder.io/qwik@1.20.0(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0))) shiki: - specifier: ^4.3.1 - version: 4.3.1 + specifier: ^4.4.1 + version: 4.4.1 simple-icons-qwik: specifier: 16.27.0-qwik-v2 version: 16.27.0-qwik-v2 @@ -352,8 +355,8 @@ importers: specifier: 4.1.10 version: 4.1.10(@types/node@26.1.2)(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1)(sass@1.102.0)(stylus@0.62.0)(tsx@4.23.1)(typescript@7.0.2)(yaml@2.9.0))(happy-dom@20.11.1) wrangler: - specifier: ^4.115.0 - version: 4.115.0 + specifier: ^4.118.0 + version: 4.118.0 yaml: specifier: ^2.9.0 version: 2.9.0 @@ -367,6 +370,15 @@ importers: specifier: 7.0.2 version: 7.0.2 + packages/mc-status: + devDependencies: + '@types/node': + specifier: ^25.0.3 + version: 25.9.5 + typescript: + specifier: 7.0.2 + version: 7.0.2 + packages/rgbirdflop: devDependencies: '@types/node': @@ -441,32 +453,32 @@ packages: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20260722.1': - resolution: {integrity: sha512-vZOP8vIS3NwnuaO+gz0FZ7kIGeiO3bZmxV35Ph9zOXKSREhDFlH7wQ7mkCdhW3O4jnXsew+XT7b+DNEI2CcJGQ==} + '@cloudflare/workerd-darwin-64@1.20260730.1': + resolution: {integrity: sha512-+MBHmPaiTe2KajryW0T24rZvWFxb41hD3d8anNzQqHzft6vSEb18+sp0znSwxgij7ApPhSM1+vhkNg4f3YMguA==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260722.1': - resolution: {integrity: sha512-EmIQymihDq6WNdER4+LF8Qn80yqayBUpJ+tkOO7wmY8pmgfyXjIUFNXotl21AHovTeu2seR7HdVUgeN/BilCWw==} + '@cloudflare/workerd-darwin-arm64@1.20260730.1': + resolution: {integrity: sha512-SBHKntPkKvNPgaCrTe99xC1CAl8ygJDzlYfK0LbuJ1muKadIw35WnhO0wu894fKBtllsVQdNzDLee+cm0ppLSQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260722.1': - resolution: {integrity: sha512-jvZ3k9fxcnEn04s80CgIYxQfpOyAiz/8qC42DP8EBa9tR27qWyg9wmm31zIobVlrgBZn/+8NfdP73avRGcQOjQ==} + '@cloudflare/workerd-linux-64@1.20260730.1': + resolution: {integrity: sha512-ouyPOSMbiKPeSwUJUvxtMcxGAXs2J4aPE4T5ABIYX5ClcQx5j5bbHTmnqOQEY8sAuLTPjH7dY+iB6UI5ISlwwA==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260722.1': - resolution: {integrity: sha512-BOSB55SMNdy+DA5uj2WirgiNanpHGis5PVvXH1wSfvjRKr4JGgWK+EZzxz0RFUo6QjjQQC/NimEzNZ7va7jmKg==} + '@cloudflare/workerd-linux-arm64@1.20260730.1': + resolution: {integrity: sha512-YQ+Mi78U3TPdgBPtwq+Sm6rJU+Ihl2y0pjYtuuKkdmUbYzL7oLR6Xqq9wljhasnuCFICssDJaqhMep5WizYoEQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260722.1': - resolution: {integrity: sha512-sYM8YgUpKnRz2xjvdJLX1Ojzoi4MlA4gk8WTTExhGydjYB2UTs5NIbv0ZmpKgMoK9io3ixgmiW56ZnTbcWOdiA==} + '@cloudflare/workerd-windows-64@1.20260730.1': + resolution: {integrity: sha512-27fAN+vUECW1oYVc1KOcHYpkL8COM2Uxtxql7TL595kxbjoqS5yckw7NLz7bTf2pALFCZWjqXDjZGJ/xbG4ZKQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -1431,8 +1443,8 @@ packages: resolution: {integrity: sha512-W69uzPt9qAhqShl74bma9yzRqXv8FNJLz3R/R6vC4Z5rXYaZJ4W2fcITOUX33WBmKIlq6YNPrm6rWWY6tfMikw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - '@luminescent/ui-qwik@7.0.0-25': - resolution: {integrity: sha512-2zXT7+2Ep4eswyRs9kQqYbt+g5ZE0yrwtt9AKD612bv2M9mUEolevkilOWY6M10RGxLGKJCfiDsxososUHQhMA==} + '@luminescent/ui-qwik@7.0.0-28': + resolution: {integrity: sha512-jpTnmPeEge86BGUxc2rOdR4kzKwic7Nnba2/GlXCkyPcor94CZWMyR1TC2oV1PMqVbeUh1mD+BDN3RkAaV3G0A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} peerDependencies: '@luminescent/ui': 7.2.0-1 @@ -2016,44 +2028,44 @@ packages: cpu: [x64] os: [win32] - '@shikijs/colorized-brackets@4.3.1': - resolution: {integrity: sha512-hyp9iRV7iEnU/sT1DL3eLevY0wOwIeFMp6ss8U8FfWdwT5LLhv4sO5Xt6r0dD3eteeswDngBiuEv6nQzQEwv2w==} + '@shikijs/colorized-brackets@4.4.1': + resolution: {integrity: sha512-bSvqs9fNSyAhx9sa7yj1KEyBwz6LlI2LO7U0zGCKb1GkB5kttcU6l52EFi0AQDu6BnagYI3B5JAin3jCbwKhRg==} engines: {node: '>=20'} - '@shikijs/core@4.3.1': - resolution: {integrity: sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==} + '@shikijs/core@4.4.1': + resolution: {integrity: sha512-VeR2CY6Nn9/WbisoYLOQZ7HZOnwTrpBuOw4wExjqLnBCi62BNWynBUO6K2uPIASPFJwAv7cX1fUu+LrPlSstcw==} engines: {node: '>=20'} - '@shikijs/engine-javascript@4.3.1': - resolution: {integrity: sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==} + '@shikijs/engine-javascript@4.4.1': + resolution: {integrity: sha512-6U4lJBh8LTvIkEVqRHv/rr3ruwtO6IweFQt1ME1ntHJMGHS+6N86vfYGO1o8c/DtOCTia2lfhdQBtBrps1sDfQ==} engines: {node: '>=20'} - '@shikijs/engine-oniguruma@4.3.1': - resolution: {integrity: sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==} + '@shikijs/engine-oniguruma@4.4.1': + resolution: {integrity: sha512-p23RugMKss0r5DAtRJW1yAXUDl60JvhQYV20yuxei//26JyDSJefV3umyWzzwep2weblMnJGDYahuti6XkcMgA==} engines: {node: '>=20'} - '@shikijs/langs@4.3.1': - resolution: {integrity: sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==} + '@shikijs/langs@4.4.1': + resolution: {integrity: sha512-xb2kCMloBCIraIy2fS5MW0t/BxVY3q2nDyQKBoeSeq6KNrQbShHetCFlw2n35fGIJ6t3+hXDLQogP5ir9O9bvA==} engines: {node: '>=20'} - '@shikijs/primitive@4.3.1': - resolution: {integrity: sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==} + '@shikijs/primitive@4.4.1': + resolution: {integrity: sha512-ko2OfDoG89YuQ7xL5LtcQiWKb7NIv1Ephb7g48TVU198OzAMLC8lXVEwaJGHK4sUMYrfAGJDqYmNLOLiW/Kz8w==} engines: {node: '>=20'} - '@shikijs/rehype@4.3.1': - resolution: {integrity: sha512-oshrlfUF3VPUJfnp5K1lLwsS/SRBKrIxONpdWebSKZXdBE3UsZnxgqpvRUA8UsofS7vmjFOCAHIT71ECbmOxTw==} + '@shikijs/rehype@4.4.1': + resolution: {integrity: sha512-lK6tl7wIQZ0vRynHs6w82KEFhtBhQI1zfPB3iDXXVVvDw8OLLKUYT1e8qK3QDYiMp8TIkq/q9PYVoIl+8YhCRQ==} engines: {node: '>=20'} - '@shikijs/themes@4.3.1': - resolution: {integrity: sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==} + '@shikijs/themes@4.4.1': + resolution: {integrity: sha512-wudOaoFro+/Zl9gQv2W1Ur5XlVduqvTuYLI483Xi0wgc1A+cy1hfB2r6ac6ufBgF+ID7KJEW7L41MHrzQ4wH+w==} engines: {node: '>=20'} - '@shikijs/transformers@4.3.1': - resolution: {integrity: sha512-z6ir0bGDgWcF2FduktEfPgIsdOtIlDiLAjFBgBzE42Q9xHbkkIXZtORHzlLVB71iZP9elEcqKg6keajvOUwE2A==} + '@shikijs/transformers@4.4.1': + resolution: {integrity: sha512-Sb9Eehas+5EhClpFgNuklwY3aWf354FLaKRCiAWmjdNbHAjoQUpv6WmSj+N19eTXO6GLIWh1dIOH9dxyauhVWw==} engines: {node: '>=20'} - '@shikijs/types@4.3.1': - resolution: {integrity: sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==} + '@shikijs/types@4.4.1': + resolution: {integrity: sha512-GOwCLQDHM5EjGUWNPrhzJbr6JP8V/Dx/CDVkWvbZ1Avw5JFnNUckrgbLmE07qtg4WlW7Q7QFndhjIkeU9XMPvw==} engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': @@ -2182,8 +2194,8 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - '@types/better-sqlite3@7.6.13': - resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/better-sqlite3@9.6.0': + resolution: {integrity: sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -2230,8 +2242,8 @@ packages: '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} - '@types/three@0.185.1': - resolution: {integrity: sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==} + '@types/three@0.185.3': + resolution: {integrity: sha512-8TqTn1+fjPWuJ4mR6Igtg56DCf9b5EeAlwhb5xaa6WlBrsg7SvG0NQbyctGRkHCKwg0uftfCspOEsTXelgktMA==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2568,11 +2580,11 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.17: - resolution: {integrity: sha512-w+aeW/mkgM4PyRMOJCgi3fOrTm5Q8QY1OSfn2TO2iuDj3ezIHqejmuxbjfPrqUkgqRew1iqkyAn0tr0ZwHD9+w==} + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - brace-expansion@5.0.8: - resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} buffer-from@1.1.2: @@ -2832,8 +2844,8 @@ packages: sqlite3: optional: true - enhanced-resolve@5.24.4: - resolution: {integrity: sha512-GVoi+ICHocoOIU7qVVM48wOJziRsqrsyqlI0Ce0LdowRn6v3bcH2zUa9kp85ncx0nwIb9/HOCOLS3fdThDG/XQ==} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -3067,8 +3079,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.4: - resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + jose@6.2.6: + resolution: {integrity: sha512-HwMtbJjMw8rC8dUTwCNilHJD+fxTeKM3JV1eprSmTjS41qwXSSt6exJXgyPK1QOu0jB9eDYLESRDkB3qaT3jnw==} js-binary-schema-parser@2.0.3: resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} @@ -3466,10 +3478,9 @@ packages: engines: {node: '>=4'} hasBin: true - miniflare@4.20260722.1: - resolution: {integrity: sha512-FJIg4omaCb2wwSyOeRosEdmVRi3JzGAOOH3pa3twmmtvWECl6BVZMIDwJbjByLKRyU+mrfk0M/n3oSiojFZvSA==} + miniflare@5.20260730.0-alpha: + resolution: {integrity: sha512-8/dspSXDshP6nSkCpjKO7BYc2qZoYSXm7iM+QxY7qJyJpAB3onnQSaiu0cvKJlfuMGwULl55hG69FJCcCMXU1Q==} engines: {node: '>=22.0.0'} - hasBin: true minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} @@ -3512,8 +3523,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-api@8.9.0: - resolution: {integrity: sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==} + node-addon-api@8.9.1: + resolution: {integrity: sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg==} engines: {node: ^18 || ^20 || >= 21} nth-check@2.1.1: @@ -3804,8 +3815,8 @@ packages: resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} engines: {node: '>= 0.4'} - shiki@4.3.1: - resolution: {integrity: sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==} + shiki@4.4.1: + resolution: {integrity: sha512-rFP+iYKzjLEIqiMiKANhARqiAbk4deDhWnBtnUO/K0D0dPxMGDH4N0FVfBY/VeI+lPrV4wNGCHQZp7EOr7NNBw==} engines: {node: '>=20'} siginfo@2.0.0: @@ -4078,17 +4089,17 @@ packages: engines: {node: '>=8'} hasBin: true - workerd@1.20260722.1: - resolution: {integrity: sha512-NycKuc1x2onvsRfGGpM093vRlLFU2zHDAM0+APpccfg4+gZxDGCH27RmdDvkeBuoZyYqgLo3oAfF6re4mvC3vQ==} + workerd@1.20260730.1: + resolution: {integrity: sha512-zmfNIjwYSWFY5chGBOjWtH3xAE7p97FTC6vR4Ep98290ho6AeAR/NVcBD274YCLEUYzqm8yxdtZlxMybU8a3jA==} engines: {node: '>=16'} hasBin: true - wrangler@4.115.0: - resolution: {integrity: sha512-+upG2VW66M1sjb43yzgUZ6Ss8iYpJ6+7F3U4GF8TY5EYd+08sYdS54d24AEwCnhuef3J9KlSPusqiqR9WYy1UA==} + wrangler@4.118.0: + resolution: {integrity: sha512-9pkBw/b8zWqGx2S+oLhgHMR1M/4VOE8SynUFABnGWiSFGlcOQ4xiI/B71Xf66RYP2xzngU37IQFPtUruij3lYw==} engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^5.20260722.1 + '@cloudflare/workers-types': ^5.20260730.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -4152,7 +4163,7 @@ snapshots: '@auth/core@0.41.3': dependencies: '@panva/hkdf': 1.2.1 - jose: 6.2.4 + jose: 6.2.6 oauth4webapi: 3.8.6 preact: 10.24.3 preact-render-to-string: 6.5.11(preact@10.24.3) @@ -4228,25 +4239,25 @@ snapshots: '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260722.1)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260722.1 + workerd: 1.20260730.1 - '@cloudflare/workerd-darwin-64@1.20260722.1': + '@cloudflare/workerd-darwin-64@1.20260730.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260722.1': + '@cloudflare/workerd-darwin-arm64@1.20260730.1': optional: true - '@cloudflare/workerd-linux-64@1.20260722.1': + '@cloudflare/workerd-linux-64@1.20260730.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260722.1': + '@cloudflare/workerd-linux-arm64@1.20260730.1': optional: true - '@cloudflare/workerd-windows-64@1.20260722.1': + '@cloudflare/workerd-windows-64@1.20260730.1': optional: true '@cspotcode/source-map-support@0.8.1': @@ -4826,7 +4837,7 @@ snapshots: '@luminescent/icons-qwik@0.1.1': {} - '@luminescent/ui-qwik@7.0.0-25(@luminescent/ui@7.2.0-1)': + '@luminescent/ui-qwik@7.0.0-28(@luminescent/ui@7.2.0-1)': dependencies: '@luminescent/ui': 7.2.0-1 @@ -5208,58 +5219,58 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.3': optional: true - '@shikijs/colorized-brackets@4.3.1': + '@shikijs/colorized-brackets@4.4.1': dependencies: - shiki: 4.3.1 + shiki: 4.4.1 - '@shikijs/core@4.3.1': + '@shikijs/core@4.4.1': dependencies: - '@shikijs/primitive': 4.3.1 - '@shikijs/types': 4.3.1 + '@shikijs/primitive': 4.4.1 + '@shikijs/types': 4.4.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@4.3.1': + '@shikijs/engine-javascript@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.6 - '@shikijs/engine-oniguruma@4.3.1': + '@shikijs/engine-oniguruma@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@4.3.1': + '@shikijs/langs@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 - '@shikijs/primitive@4.3.1': + '@shikijs/primitive@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 - '@shikijs/rehype@4.3.1': + '@shikijs/rehype@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 '@types/hast': 3.0.5 hast-util-to-string: 3.0.1 - shiki: 4.3.1 + shiki: 4.4.1 unified: 11.0.5 unist-util-visit: 5.1.0 - '@shikijs/themes@4.3.1': + '@shikijs/themes@4.4.1': dependencies: - '@shikijs/types': 4.3.1 + '@shikijs/types': 4.4.1 - '@shikijs/transformers@4.3.1': + '@shikijs/transformers@4.4.1': dependencies: - '@shikijs/core': 4.3.1 - '@shikijs/types': 4.3.1 + '@shikijs/core': 4.4.1 + '@shikijs/types': 4.4.1 - '@shikijs/types@4.3.1': + '@shikijs/types@4.4.1': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 @@ -5275,7 +5286,7 @@ snapshots: '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.24.4 + enhanced-resolve: 5.24.5 jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 @@ -5365,7 +5376,7 @@ snapshots: '@types/aria-query@5.0.4': {} - '@types/better-sqlite3@7.6.13': + '@types/better-sqlite3@9.6.0': dependencies: '@types/node': 26.1.2 @@ -5418,7 +5429,7 @@ snapshots: '@types/stats.js@0.17.4': {} - '@types/three@0.185.1': + '@types/three@0.185.3': dependencies: '@dimforge/rapier3d-compat': 0.12.0 '@tweenjs/tween.js': 23.1.3 @@ -5640,19 +5651,19 @@ snapshots: better-sqlite3@13.0.2: dependencies: - node-addon-api: 8.9.0 + node-addon-api: 8.9.1 blake3-wasm@2.1.5: {} boolbase@1.0.0: {} - brace-expansion@1.1.17: + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 optional: true - brace-expansion@5.0.8: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -5795,12 +5806,12 @@ snapshots: esbuild: 0.25.12 tsx: 4.23.1 - drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@13.0.2): + drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.2): optionalDependencies: - '@types/better-sqlite3': 7.6.13 + '@types/better-sqlite3': 9.6.0 better-sqlite3: 13.0.2 - enhanced-resolve@5.24.4: + enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -6159,7 +6170,7 @@ snapshots: jiti@2.7.0: {} - jose@6.2.4: {} + jose@6.2.6: {} js-binary-schema-parser@2.0.3: {} @@ -6784,12 +6795,12 @@ snapshots: mime@1.6.0: optional: true - miniflare@4.20260722.1: + miniflare@5.20260730.0-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.35.2 undici: 7.28.0 - workerd: 1.20260722.1 + workerd: 1.20260730.1 ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -6798,11 +6809,11 @@ snapshots: minimatch@10.2.6: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.17 + brace-expansion: 1.1.18 optional: true minimist@1.2.8: {} @@ -6841,7 +6852,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-addon-api@8.9.0: {} + node-addon-api@8.9.1: {} nth-check@2.1.1: dependencies: @@ -7317,14 +7328,14 @@ snapshots: shell-quote@1.10.0: {} - shiki@4.3.1: + shiki@4.4.1: dependencies: - '@shikijs/core': 4.3.1 - '@shikijs/engine-javascript': 4.3.1 - '@shikijs/engine-oniguruma': 4.3.1 - '@shikijs/langs': 4.3.1 - '@shikijs/themes': 4.3.1 - '@shikijs/types': 4.3.1 + '@shikijs/core': 4.4.1 + '@shikijs/engine-javascript': 4.4.1 + '@shikijs/engine-oniguruma': 4.4.1 + '@shikijs/langs': 4.4.1 + '@shikijs/themes': 4.4.1 + '@shikijs/types': 4.4.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 @@ -7687,24 +7698,24 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - workerd@1.20260722.1: + workerd@1.20260730.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260722.1 - '@cloudflare/workerd-darwin-arm64': 1.20260722.1 - '@cloudflare/workerd-linux-64': 1.20260722.1 - '@cloudflare/workerd-linux-arm64': 1.20260722.1 - '@cloudflare/workerd-windows-64': 1.20260722.1 + '@cloudflare/workerd-darwin-64': 1.20260730.1 + '@cloudflare/workerd-darwin-arm64': 1.20260730.1 + '@cloudflare/workerd-linux-64': 1.20260730.1 + '@cloudflare/workerd-linux-arm64': 1.20260730.1 + '@cloudflare/workerd-windows-64': 1.20260730.1 - wrangler@4.115.0: + wrangler@4.118.0: dependencies: '@cloudflare/kv-asset-handler': 0.5.0 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260722.1) + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1) blake3-wasm: 2.1.5 esbuild: 0.28.1 - miniflare: 4.20260722.1 + miniflare: 5.20260730.0-alpha path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260722.1 + workerd: 1.20260730.1 optionalDependencies: fsevents: 2.3.3 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a62a3410..588e3a67 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,7 +9,7 @@ allowBuilds: workerd: true minimumReleaseAgeExclude: - - '@luminescent/ui-qwik@7.0.0-25' + - '@luminescent/ui-qwik@7.0.0-25 || 7.0.0-28' - '@luminescent/ui@7.0.2 || 7.1.0 || 7.2.0-1' - lucide-icons-qwik@1.24.0-qwik-v2 || 1.25.0-qwik-v2 - happy-dom@20.11.1 @@ -17,6 +17,7 @@ minimumReleaseAgeExclude: - better-sqlite3@13.0.2 - miniflare@4.20260722.1 - wrangler@4.115.0 + - '@types/better-sqlite3@9.6.0' onlyBuiltDependencies: - better-sqlite3 diff --git a/release-please-config.json b/release-please-config.json index 5dfa99be..47162764 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -19,6 +19,12 @@ "component": "analyze", "include-component-in-tag": true, "extra-files": ["package.json"] + }, + "packages/mc-status": { + "package-name": "mc-status", + "component": "mc-status", + "include-component-in-tag": true, + "extra-files": ["package.json"] } }, "changelog-sections": [ diff --git a/src/components/Elements/MotdPreviewCard.tsx b/src/components/Elements/MotdPreviewCard.tsx index 135a8e13..915f3256 100644 --- a/src/components/Elements/MotdPreviewCard.tsx +++ b/src/components/Elements/MotdPreviewCard.tsx @@ -6,6 +6,7 @@ const ImgMcPing5 = '/minecraft/ping_5.png'; export interface MotdPreviewCardProps { icon?: string; label?: string; + version?: string | null; playersOnline?: number; playersMax?: number; class?: string; @@ -15,6 +16,7 @@ export const MotdPreviewCard = component$( ({ icon, label = 'Minecraft Server', + version, playersOnline = 42, playersMax = 100, class: className = '', @@ -40,7 +42,15 @@ export const MotdPreviewCard = component$( > {label} -
+
+ {version && ( + + {version} + + )} diff --git a/src/components/Elements/MotdText.tsx b/src/components/Elements/MotdText.tsx new file mode 100644 index 00000000..a061f8d9 --- /dev/null +++ b/src/components/Elements/MotdText.tsx @@ -0,0 +1,60 @@ +import { component$, useVisibleTask$ } from '@qwik.dev/core'; +import { parseMotdLine, shadowColor } from '~/util/motd'; +import { getFormattingClasses } from '~/components/rgbirdflop/preview'; +import { obfuscateText } from '~/util/rgb/obfuscator'; + +export interface MotdTextProps { + text: string; + class?: string; +} + +export function renderMotdLine(line: string) { + const runs = parseMotdLine(line); + if (runs.length === 0) return ; + return runs.map((run, i) => ( + + {run.text} + + )); +} + +export const MotdText = component$( + ({ text, class: className = '' }) => { + // Animate obfuscated (&k / ยงk) text by scrambling characters + // oxlint-disable-next-line qwik/no-use-visible-task + useVisibleTask$((taskCtx) => { + const id = setInterval(() => { + document.querySelectorAll('.motd-obf').forEach((el) => { + const original = el.getAttribute('data-obf') ?? el.textContent ?? ''; + el.textContent = obfuscateText(original); + }); + }, 70); + taskCtx.cleanup(() => clearInterval(id)); + }); + + const lines = text.split(/\r?\n/); + + return ( +
+ {lines.map((line, idx) => ( +
+ {renderMotdLine(line)} +
+ ))} +
+ ); + } +); + +export default MotdText; diff --git a/src/components/Elements/Nav.tsx b/src/components/Elements/Nav.tsx index 4adfd777..52c1cf0a 100644 --- a/src/components/Elements/Nav.tsx +++ b/src/components/Elements/Nav.tsx @@ -23,6 +23,7 @@ import Activity from 'lucide-icons-qwik/icons/Activity'; import AppWindow from 'lucide-icons-qwik/icons/AppWindow'; import Settings from 'lucide-icons-qwik/icons/Settings'; import Loader2 from 'lucide-icons-qwik/icons/Loader2'; +import Gamepad2 from 'lucide-icons-qwik/icons/Gamepad2'; import { inlineTranslate, useSpeakConfig, useSpeakLocale } from 'qwik-speak'; import { useSession, useSignIn, useSignOut } from '~/routes/plugin@auth'; @@ -92,7 +93,7 @@ export default component$(() => { class="lum-bg-transparent hover:lum-bg-nav-bg hidden sm:flex" > - {t('nav.hosting.title@@Hosting')} + {t('nav.hosting.title@@Hosting')} { > {t('nav.resources.analyze.title@@Analyze')} + + {t('nav.resources.status.title@@Server Status')} + { {t('nav.resources.more@@More Resources')} + + {' '} + {t('nav.resources.serverList.title@@Server List')} + (({ hidden, value, class: className }) => { +}>(({ hidden, value, compact, class: className }) => { const t = inlineTranslate(); const copiedTitle = t('nav.copied.title@@Copied to clipboard!'); const copiedDescription = t( @@ -18,6 +20,37 @@ export default component$<{ const notifications = useContext(NotificationContext); + const onClick = () => { + const notification = new Notification() + .setTitle(copiedTitle) + .setDescription(copiedDescription) + .setBgColor('lum-grad-bg-green/50'); + navigator.clipboard.writeText(value).catch((err: unknown) => { + notification + .setTitle(copyFailedTitle) + .setDescription(err instanceof Error ? err.message : String(err)) + .setBgColor('lum-grad-bg-red/50') + .setPersist(true); + }); + notifications.push(notification.toJSON()); + }; + + if (compact) { + return ( + + ); + } + return (
{ - const notification = new Notification() - .setTitle(copiedTitle) - .setDescription(copiedDescription) - .setBgColor('lum-grad-bg-green/50'); - navigator.clipboard.writeText(value).catch((err: unknown) => { - notification - .setTitle(copyFailedTitle) - .setDescription(err instanceof Error ? err.message : String(err)) - .setBgColor('lum-grad-bg-red/50') - .setPersist(true); - }); - notifications.push(notification.toJSON()); - }} + onClick$={onClick} />
); diff --git a/src/components/Elements/Pagination.tsx b/src/components/Elements/Pagination.tsx new file mode 100644 index 00000000..a6d45b62 --- /dev/null +++ b/src/components/Elements/Pagination.tsx @@ -0,0 +1,115 @@ +import { component$, type QRL } from '@qwik.dev/core'; +import ChevronLeft from 'lucide-icons-qwik/icons/ChevronLeft'; +import ChevronRight from 'lucide-icons-qwik/icons/ChevronRight'; +import { SelectMenu } from '@luminescent/ui-qwik'; +import { inlineTranslate } from 'qwik-speak'; + +export interface PaginationProps { + page: number; + perPage?: number; + totalPages: number; + totalCount: number; + rowsLength: number; + updateURL: QRL<(params: Record) => void>; + itemLabel?: string; + showPerPage?: boolean; + perPageOptions?: number[]; +} + +export default component$( + ({ + page, + perPage, + totalPages, + totalCount, + rowsLength, + updateURL, + itemLabel, + showPerPage = true, + perPageOptions = [10, 20, 50, 100], + }) => { + const t = inlineTranslate(); + const labelText = itemLabel ?? t('pagination.items@@items'); + + return ( +
+

+ {`${t('pagination.totalPrefix@@Total')} ${labelText}: ${rowsLength}/${totalCount}`} + {totalPages > 1 && + ` - ${t('pagination.pageInfo@@Page ')}${page} ${t('pagination.of@@of')} ${totalPages}`} +

+
+ +
+ {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + let pageNum: number; + if (totalPages <= 5) { + pageNum = i + 1; + } else if (page <= 3) { + pageNum = i + 1; + } else if (page >= totalPages - 2) { + pageNum = totalPages - 4 + i; + } else { + pageNum = page - 2 + i; + } + return ( + + ); + })} +
+ +
+ {showPerPage && perPage !== undefined && ( +
+

+ {t('pagination.perPage@@Per page:')} +

+ { + const newPerPage = parseInt(el.value, 10); + void updateURL({ perPage: newPerPage, page: 1 }); + }} + title={t('pagination.perPageTitle@@Items per page')} + values={perPageOptions.map((opt) => ({ + name: String(opt), + value: String(opt), + }))} + /> +
+ )} +
+ ); + } +); diff --git a/src/components/ServerList/BBCodeEditor.tsx b/src/components/ServerList/BBCodeEditor.tsx new file mode 100644 index 00000000..4329624b --- /dev/null +++ b/src/components/ServerList/BBCodeEditor.tsx @@ -0,0 +1,388 @@ +import { + $, + component$, + useOnDocument, + useSignal, + type QRL, +} from '@qwik.dev/core'; +import { ButtonContainer, ColorPicker } from '@luminescent/ui-qwik'; +import Bold from 'lucide-icons-qwik/icons/Bold'; +import Italic from 'lucide-icons-qwik/icons/Italic'; +import Underline from 'lucide-icons-qwik/icons/Underline'; +import Strikethrough from 'lucide-icons-qwik/icons/Strikethrough'; +import Link2 from 'lucide-icons-qwik/icons/Link2'; +import Image from 'lucide-icons-qwik/icons/Image'; +import Palette from 'lucide-icons-qwik/icons/Palette'; +import Type from 'lucide-icons-qwik/icons/Type'; +import AlignCenter from 'lucide-icons-qwik/icons/AlignCenter'; +import Quote from 'lucide-icons-qwik/icons/Quote'; +import Code from 'lucide-icons-qwik/icons/Code'; +import List from 'lucide-icons-qwik/icons/List'; +import Eye from 'lucide-icons-qwik/icons/Eye'; +import EyeOff from 'lucide-icons-qwik/icons/EyeOff'; +import Check from 'lucide-icons-qwik/icons/Check'; +import { renderBBCode } from '~/util/serverlist/bbcode'; + +interface BBCodeEditorProps { + value: string; + onChange$: QRL<(value: string) => void>; + id?: string; + maxLength?: number; + placeholder?: string; +} + +type Menu = 'link' | 'image' | 'color' | 'size' | null; + +const SIZE_PRESETS = [ + { label: 'Small', value: 12 }, + { label: 'Normal', value: 16 }, + { label: 'Large', value: 22 }, + { label: 'Huge', value: 28 }, +]; + +export default component$( + ({ value, onChange$, id, maxLength, placeholder }) => { + const ref = useSignal(); + const rootRef = useSignal(); + const showPreview = useSignal(false); + const current = useSignal(value); + + // Which popover is open, and the textarea selection captured when it opened. + const menu = useSignal(null); + const selStart = useSignal(0); + const selEnd = useSignal(0); + + // Popover field state + const color = useSignal('#54daf4'); + const linkUrl = useSignal('https://'); + const imgUrl = useSignal('https://'); + + // Close popovers on outside click. + useOnDocument( + 'pointerdown', + $((e) => { + const target = e.target as Node | null; + if (rootRef.value && target && !rootRef.value.contains(target)) + menu.value = null; + }) + ); + + // Core insert: wrap [start,end) of the textarea with open/close (or a placeholder). + const insertAt = $( + async ( + open: string, + close: string, + placeholderText: string, + start: number, + end: number + ) => { + const el = ref.value; + if (!el) return; + const text = el.value; + const selected = text.slice(start, end) || placeholderText; + const next = + text.slice(0, start) + open + selected + close + text.slice(end); + + el.value = next; + current.value = next; + await onChange$(next); + + const innerStart = start + open.length; + const innerEnd = innerStart + selected.length; + el.focus(); + el.setSelectionRange(innerStart, innerEnd); + } + ); + + // Direct (no-input) tags: operate on the live selection. + const wrap = $((open: string, close: string, placeholderText = '') => { + const el = ref.value; + if (!el) return; + return insertAt( + open, + close, + placeholderText, + el.selectionStart, + el.selectionEnd + ); + }); + + // Open a popover, capturing the current selection first (the buttons use + // preventdefault:mousedown so the textarea keeps focus + selection). + const openMenu = $((which: Menu) => { + const el = ref.value; + if (el) { + selStart.value = el.selectionStart; + selEnd.value = el.selectionEnd; + } + menu.value = menu.value === which ? null : which; + }); + + const applyFromMenu = $( + async (open: string, close: string, placeholderText: string) => { + await insertAt( + open, + close, + placeholderText, + selStart.value, + selEnd.value + ); + menu.value = null; + } + ); + + return ( +
+
+ {/* Toolbar */} +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + {/* Popovers */} + {menu.value === 'link' && ( +
+ Insert link +
+ (linkUrl.value = el.value)} + placeholder="https://example.com" + /> + +
+
+ )} + + {menu.value === 'image' && ( +
+ Insert image +
+ (imgUrl.value = el.value)} + placeholder="https://image.png" + /> + +
+
+ )} + + {menu.value === 'color' && ( +
+ Text color + (color.value = c)} + horizontal + /> + +
+ )} + + {menu.value === 'size' && ( +
+ Text size + {SIZE_PRESETS.map((s) => ( + + ))} +
+ )} +
+ + {showPreview.value ? ( +
Nothing to preview yetโ€ฆ' + } + /> + ) : ( +