From 33b084b5756544ffd8f6857100fdebaee9e9e50e Mon Sep 17 00:00:00 2001 From: Mari Date: Thu, 30 Dec 2021 03:16:51 -0500 Subject: [PATCH] Migrate to graphile-migrate and pg directly because prisma failed me. --- .env.example | 6 +- .gitignore | 28 +- .gmrc | 134 ++ .idea/dataSources.xml | 14 + .idea/sqldialects.xml | 6 + .idea/watcherTasks.xml | 43 +- .../committed/000001-discordchannel-table.sql | 32 + ...000002-gender-table-and-initial-values.sql | 21 + migrations/committed/000003-player-table.sql | 31 + .../committed/000004-discorduser-table.sql | 16 + .../committed/000005-unittier-table.sql | 16 + migrations/committed/000006-unit-table.sql | 24 + .../committed/000007-summonedunit-table.sql | 59 + .../committed/000008-all-the-procedures.sql | 367 ++++ .../000009-fix-webhooktoken-size.sql | 6 + .../000010-fix-function-volatility.sql | 10 + .../000011-fix-some-more-procedures.sql | 197 +++ .../000012-fix-command_join-again.sql | 33 + .../000013-pull-command-skeleton.sql | 66 + migrations/dump.sql | 542 ++++++ package-lock.json | 1533 +++++++++++++++-- package.json | 18 +- prisma/schema.prisma | 185 -- src/BaseServer.ts | 10 +- src/CookieHelpers.ts | 9 +- src/GameServer.ts | 39 +- src/app.ts | 15 +- src/commands/debug/UnjoinCommand.ts | 46 + src/commands/game/JoinCommand.ts | 59 +- src/commands/game/PullCommand.ts | 28 +- .../permissions/ChannelPermissions.ts | 26 - src/queries/ChannelManager.ts | 95 - src/queries/ErrorCodes.ts | 48 + src/queries/Prisma.ts | 9 - src/queries/QueryHelpers.ts | 13 + src/queries/UserManager.ts | 133 -- src/tools/LoadGenders.ts | 44 - src/tools/LoadJsonWebhooks.ts | 62 +- tsconfig.json | 2 +- 39 files changed, 3259 insertions(+), 766 deletions(-) create mode 100644 .gmrc create mode 100644 .idea/dataSources.xml create mode 100644 .idea/sqldialects.xml create mode 100644 migrations/committed/000001-discordchannel-table.sql create mode 100644 migrations/committed/000002-gender-table-and-initial-values.sql create mode 100644 migrations/committed/000003-player-table.sql create mode 100644 migrations/committed/000004-discorduser-table.sql create mode 100644 migrations/committed/000005-unittier-table.sql create mode 100644 migrations/committed/000006-unit-table.sql create mode 100644 migrations/committed/000007-summonedunit-table.sql create mode 100644 migrations/committed/000008-all-the-procedures.sql create mode 100644 migrations/committed/000009-fix-webhooktoken-size.sql create mode 100644 migrations/committed/000010-fix-function-volatility.sql create mode 100644 migrations/committed/000011-fix-some-more-procedures.sql create mode 100644 migrations/committed/000012-fix-command_join-again.sql create mode 100644 migrations/committed/000013-pull-command-skeleton.sql create mode 100644 migrations/dump.sql delete mode 100644 prisma/schema.prisma create mode 100644 src/commands/debug/UnjoinCommand.ts delete mode 100644 src/commands/permissions/ChannelPermissions.ts delete mode 100644 src/queries/ChannelManager.ts create mode 100644 src/queries/ErrorCodes.ts delete mode 100644 src/queries/Prisma.ts create mode 100644 src/queries/QueryHelpers.ts delete mode 100644 src/queries/UserManager.ts delete mode 100644 src/tools/LoadGenders.ts diff --git a/.env.example b/.env.example index 7b59e11..a1c45be 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,8 @@ DISCORD_BOT_TOKEN= DISCORD_PUBLIC_KEY= DISCORD_CLIENT_SECRET= DISCORD_APP_ID= -HTTP_PORT=5244 \ No newline at end of file +COOKIE_SECRET= +HTTP_PORT=5244 +DATABASE_URL= +SHADOW_DATABASE_URL= +ROOT_DATABASE_URL= diff --git a/.gitignore b/.gitignore index 09a1f77..8510ec7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,30 @@ /build/ /node_modules/ .env -/runtime/ \ No newline at end of file +/runtime/ +/migrations/current.sql + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Editor-based Rest Client +.idea/httpRequests \ No newline at end of file diff --git a/.gmrc b/.gmrc new file mode 100644 index 0000000..ce6e4a3 --- /dev/null +++ b/.gmrc @@ -0,0 +1,134 @@ +/* + * Graphile Migrate configuration. + * + * If you decide to commit this file (recommended) please ensure that it does + * not contain any secrets (passwords, etc) - we recommend you manage these + * with environmental variables instead. + * + * This file is in JSON5 format, in VSCode you can use "JSON with comments" as + * the file format. + */ +{ + /* + * connectionString: this tells Graphile Migrate where to find the database + * to run the migrations against. + * + * RECOMMENDATION: use `DATABASE_URL` envvar instead. + */ + // "connectionString": "postgres://appuser:apppassword@host:5432/appdb", + + /* + * shadowConnectionString: like connectionString, but this is used for the + * shadow database (which will be reset frequently). + * + * RECOMMENDATION: use `SHADOW_DATABASE_URL` envvar instead. + */ + // "shadowConnectionString": "postgres://appuser:apppassword@host:5432/appdb_shadow", + + /* + * rootConnectionString: like connectionString, but this is used for + * dropping/creating the database in `graphile-migrate reset`. This isn't + * necessary, shouldn't be used in production, but helps during development. + * + * RECOMMENDATION: use `ROOT_DATABASE_URL` envvar instead. + */ + // "rootConnectionString": "postgres://adminuser:adminpassword@host:5432/postgres", + + /* + * pgSettings: key-value settings to be automatically loaded into PostgreSQL + * before running migrations, using an equivalent of `SET LOCAL TO + * ` + */ + "pgSettings": { + // "search_path": "app_public,app_private,app_hidden,public", + }, + /* + * placeholders: substituted in SQL files when compiled/executed. Placeholder + * keys should be prefixed with a colon and in all caps, like + * `:COLON_PREFIXED_ALL_CAPS`. Placeholder values should be strings. They + * will be replaced verbatim with NO ESCAPING AT ALL (this differs from how + * psql handles placeholders) so should only be used with "safe" values. This + * is useful for committing migrations where certain parameters can change + * between environments (development, staging, production) but you wish to + * use the same signed migration files for all. + * + * The special value "!ENV" can be used to indicate an environmental variable + * of the same name should be used. + * + * Graphile Migrate automatically sets the `:DATABASE_NAME` and + * `:DATABASE_OWNER` placeholders, and you should not attempt to override + * these. + */ + "placeholders": { + // ":DATABASE_VISITOR": "!ENV", // Uses process.env.DATABASE_VISITOR + }, + /* + * Actions allow you to run scripts or commands at certain points in the + * migration lifecycle. SQL files are ran against the database directly. + * "command" actions are ran with the following environmental variables set: + * + * - GM_DBURL: the PostgreSQL URL of the database being migrated + * - GM_DBNAME: the name of the database from GM_DBURL + * - GM_DBUSER: the user from GM_DBURL + * - GM_SHADOW: set to 1 if the shadow database is being migrated, left unset + * otherwise + * + * If "shadow" is unspecified, the actions will run on events to both shadow + * and normal databases. If "shadow" is true the action will only run on + * actions to the shadow DB, and if false only on actions to the main DB. + */ + + /* + * afterReset: actions executed after a `graphile-migrate reset` command. + */ + "afterReset": [ + // "afterReset.sql", + // { "_": "command", "command": "graphile-worker --schema-only" }, + ], + /* + * afterAllMigrations: actions executed once all migrations are complete. + */ + "afterAllMigrations": [ + // { + // "_": "command", + // "shadow": true, + // "command": "if [ \"$IN_TESTS\" != \"1\" ]; then ./scripts/dump-db; fi", + // }, + ], + /* + * afterCurrent: actions executed once the current migration has been + * evaluated (i.e. in watch mode). + */ + "afterCurrent": [ + // { + // "_": "command", + // "shadow": true, + // "command": "if [ \"$IN_TESTS\" = \"1\" ]; then ./scripts/test-seed; fi", + // }, + ], + /* + * blankMigrationContent: content to be written to the current migration + * after commit. NOTE: this should only contain comments. + */ + // "blankMigrationContent": "-- Write your migration here\n", + + /****************************************************************************\ + *** *** + *** You probably don't want to edit anything below here. *** + *** *** + \****************************************************************************/ + + /* + * manageGraphileMigrateSchema: if you set this false, you must be sure to + * keep the graphile_migrate schema up to date yourself. We recommend you + * leave it at its default. + */ + // "manageGraphileMigrateSchema": true, + + /* + * migrationsFolder: path to the folder in which to store your migrations. + */ + // migrationsFolder: "./migrations", + + "//generatedWith": "1.2.0" +} diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..be5bec9 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,14 @@ + + + + + postgresql + true + AUTOMATIC + false + org.postgresql.Driver + jdbc:postgresql://localhost:5432/gacha-prod + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..6df4889 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml index 624d8f1..afc2f4e 100644 --- a/.idea/watcherTasks.xml +++ b/.idea/watcherTasks.xml @@ -1,45 +1,4 @@ - - - - - - - - + \ No newline at end of file diff --git a/migrations/committed/000001-discordchannel-table.sql b/migrations/committed/000001-discordchannel-table.sql new file mode 100644 index 0000000..f1dbd04 --- /dev/null +++ b/migrations/committed/000001-discordchannel-table.sql @@ -0,0 +1,32 @@ +--! Previous: - +--! Hash: sha1:183d67042c756db13c07c3f3c6b8bd5301d47239 +--! Message: DiscordChannel table + +--- Table used to manage Discord channels known to the server and permissions thereon. +CREATE TABLE IF NOT EXISTS DiscordChannel +( + --- The ID of the channel in Discord, as a decimal string. + discordId VARCHAR(20) PRIMARY KEY NOT NULL, + --- The last known name of this channel. + name VARCHAR(100) NOT NULL, + --- True if this channel should be used to broadcast public game events. + broadcastGame BOOLEAN NOT NULL DEFAULT FALSE, + --- True if this channel should be used to send logs. + sendLogs BOOLEAN NOT NULL DEFAULT FALSE, + --- True if this channel can accept game commands. + acceptGameCommands BOOLEAN NOT NULL DEFAULT FALSE, + --- True if this channel can accept admin commands. + acceptAdminCommands BOOLEAN NOT NULL DEFAULT FALSE, + --- The priority of this channel when slowing down to account for rate limits. Higher is more important. + priority SMALLINT NOT NULL DEFAULT 0, + --- The snowflake ID of the guild in which this channel exists, if it's known. + guildId VARCHAR(20) DEFAULT NULL, + --- The snowflake ID of the webhook used to post to this channel. Nulled out if the webhook 404s. + webhookId VARCHAR(20) DEFAULT NULL, + --- The webhook token used to post to this channel. Nulled out if the webhook 404s. + webhookToken VARCHAR(20) DEFAULT NULL, + --- Verifies that the webhook ID and token are always set or unset together. + CONSTRAINT DiscordChannel_WebhookPair CHECK ( + (webhookId IS NULL AND webhookToken IS NULL) + OR (webhookId IS NOT NULL AND webhookToken IS NOT NULL)) +); diff --git a/migrations/committed/000002-gender-table-and-initial-values.sql b/migrations/committed/000002-gender-table-and-initial-values.sql new file mode 100644 index 0000000..058eb3e --- /dev/null +++ b/migrations/committed/000002-gender-table-and-initial-values.sql @@ -0,0 +1,21 @@ +--! Previous: sha1:183d67042c756db13c07c3f3c6b8bd5301d47239 +--! Hash: sha1:848f1f09a7cd93047aa139d0c4fc203f9ab6ccc4 +--! Message: Gender table and initial values + +--- Table of user genders. +CREATE TABLE IF NOT EXISTS Gender +( + --- The internal ID, usually a few lower-case characters roughly representing this gender. + id VARCHAR(8) PRIMARY KEY NOT NULL, + --- The name of this gender for use in tables et al. This should be the name of the gender + --- (e.g., "Female", "Male", "Nonbinary") not the name for a person of that gender. + name VARCHAR(100) UNIQUE NOT NULL +); + +--- Default genders. +INSERT INTO Gender + (id, name) +VALUES ('f', 'Female'), + ('nb', 'Non-binary'), + ('m', 'Male') +ON CONFLICT DO NOTHING; diff --git a/migrations/committed/000003-player-table.sql b/migrations/committed/000003-player-table.sql new file mode 100644 index 0000000..2ce087d --- /dev/null +++ b/migrations/committed/000003-player-table.sql @@ -0,0 +1,31 @@ +--! Previous: sha1:848f1f09a7cd93047aa139d0c4fc203f9ab6ccc4 +--! Hash: sha1:d16f7d7c74ee0a1f58ac87a09de124d165088661 +--! Message: Player table + +--- Table of in-game user data structures. +CREATE TABLE IF NOT EXISTS Player +( + --- The internal ID associated with this account. + --- It's separate from the Discord ID associated with this account. This supports a few things: + --- 1) We can move the game off of Discord, or add the ability to play it as a separate phone app or webapp, + --- without losing our database. + --- 2) If necessary, we can support having multiple Discord users associated with the same user account, to + --- support multi-account play. + --- 3) If necessary, we can support changing the Discord user associated with a user account, if for any reason + --- they are no longer using the old account and want to switch to a new account. + id SERIAL PRIMARY KEY NOT NULL, + --- The user's name, for the purposes of the game. This is completely separate from both their username and nickname + --- as Discord sees it, though it defaults to their nickname at time of joining. It does not have to be unique, and + --- can be changed at any time. + name VARCHAR(100) NOT NULL, + --- The user's gender, for the purposes of the game. This is purely cosmetic and can be changed at any time. + genderId VARCHAR(8) NOT NULL REFERENCES Gender (id) ON DELETE RESTRICT ON UPDATE CASCADE, + --- The number of units of currency this user is currently carrying. + currency INT NOT NULL, + --- The time and date at which this user joined. + joinedAt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + --- The last time this user used a command. + lastActive TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + --- The last time this user retrieved their daily resources. + lastDaily TIMESTAMP WITH TIME ZONE DEFAULT NULL +) diff --git a/migrations/committed/000004-discorduser-table.sql b/migrations/committed/000004-discorduser-table.sql new file mode 100644 index 0000000..2dc8d29 --- /dev/null +++ b/migrations/committed/000004-discorduser-table.sql @@ -0,0 +1,16 @@ +--! Previous: sha1:d16f7d7c74ee0a1f58ac87a09de124d165088661 +--! Hash: sha1:b34f19ae3e3ef87d0d7539803df087e886e1db1c +--! Message: DiscordUser table + +--- Table of information known about Discord users. +CREATE TABLE IF NOT EXISTS DiscordUser +( + --- The Discord ID this record is for. A Discord snowflake. + discordId VARCHAR(20) PRIMARY KEY NOT NULL, + --- The last known username associated with this user. + username VARCHAR(32) NOT NULL, + --- The last known discriminator associated with this user. + discriminator VARCHAR(4) NOT NULL, + --- The Player that this DiscordUser is associated with. + playerId INT REFERENCES Player (id) ON DELETE SET NULL ON UPDATE CASCADE +) diff --git a/migrations/committed/000005-unittier-table.sql b/migrations/committed/000005-unittier-table.sql new file mode 100644 index 0000000..bb92c08 --- /dev/null +++ b/migrations/committed/000005-unittier-table.sql @@ -0,0 +1,16 @@ +--! Previous: sha1:b34f19ae3e3ef87d0d7539803df087e886e1db1c +--! Hash: sha1:8243b031500fde9c022d6aada10a429496dc264d +--! Message: UnitTier table + +--- Table of definitions of unit tiers. +CREATE TABLE IF NOT EXISTS UnitTier +( + --- The internal ID associated with this tier, a few short characters. + id VARCHAR(8) NOT NULL PRIMARY KEY, + --- The human-readable name of this tier. Unique among tiers. + name VARCHAR(100) NOT NULL UNIQUE, + --- The chance of pulling a unit of this tier. + pullWeight INT NOT NULL, + --- The cost of /recalling a unit of this tier. + recallCost INT NOT NULL +) diff --git a/migrations/committed/000006-unit-table.sql b/migrations/committed/000006-unit-table.sql new file mode 100644 index 0000000..097a796 --- /dev/null +++ b/migrations/committed/000006-unit-table.sql @@ -0,0 +1,24 @@ +--! Previous: sha1:8243b031500fde9c022d6aada10a429496dc264d +--! Hash: sha1:8102b0362d2ae5b73ed1d56214f7fffa445c469e +--! Message: Unit table + +--- Table of definitions of units that can be summoned. +CREATE TABLE IF NOT EXISTS Unit +( + --- The internal ID associated with this unit. + id SERIAL NOT NULL PRIMARY KEY, + --- The name of this unit. + name VARCHAR(50) NOT NULL, + --- The subtitle of this unit. + subtitle VARCHAR(50) NOT NULL, + --- The description of this unit. + description TEXT NOT NULL, + --- The tier of this unit. + tierId VARCHAR(8) NOT NULL REFERENCES UnitTier (id) ON DELETE RESTRICT ON UPDATE CASCADE, + --- The unit's base health when summoned for the first time. + baseHealth INT NOT NULL CHECK ( baseHealth > 0 ), + --- The unit's base strength when summoned for the first time. + baseStrength INT NOT NULL CHECK ( baseStrength > 0 ), + --- The combination of Name and Subtitle is unique among units, allowing for multiple versions of a unit. + UNIQUE (name, subtitle) +) diff --git a/migrations/committed/000007-summonedunit-table.sql b/migrations/committed/000007-summonedunit-table.sql new file mode 100644 index 0000000..9560b8f --- /dev/null +++ b/migrations/committed/000007-summonedunit-table.sql @@ -0,0 +1,59 @@ +--! Previous: sha1:8102b0362d2ae5b73ed1d56214f7fffa445c469e +--! Hash: sha1:8a890a4c8807eb5a7aae90456ba1e7712cda502b +--! Message: SummonedUnit table + +--- Connection between Players and Units, indicating how and when players have summoned this unit. +CREATE TABLE IF NOT EXISTS SummonedUnit +( + --- The ID of this summoning instance. + instanceId SERIAL PRIMARY KEY NOT NULL, + --- The Player that summoned this unit at some point. + playerId INT NOT NULL REFERENCES Player (id) ON DELETE CASCADE ON UPDATE CASCADE, + --- The Unit that was summoned by this Player at some point. + unitId INT NOT NULL REFERENCES Unit (id) ON DELETE CASCADE ON UPDATE CASCADE, + --- The time and date this instance was summoned by pulling or recalling. + summonedAt TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + --- True if this instance was summoned by /pull (as opposed to /recall). + wasPulled BOOLEAN NOT NULL DEFAULT FALSE, + --- True if this instance was summoned by /recall (as opposed to /pull). + wasRecalled BOOLEAN NOT NULL DEFAULT FALSE, + --- The timestamps when this unit was resummoned because it appeared in a /pull while already summoned. + --- Does not include the initial summoning if that summoning happened via /pull. + resummonings TIMESTAMP WITH TIME ZONE ARRAY DEFAULT NULL, + --- The time and date this instance was desummoned by digesting or releasing. + desummonedAt TIMESTAMP WITH TIME ZONE DEFAULT NULL, + --- The summoned unit (friendly or enemy) that digested this unit, if a summoned unit was responsible and that + --- summoned unit has not been deleted somehow. + digestedByInstanceId INT DEFAULT NULL REFERENCES SummonedUnit (instanceId) ON DELETE SET NULL ON UPDATE CASCADE, + --- True if this instance was desummoned by being digested rather than being released. + wasDigested BOOLEAN DEFAULT NULL, + --- True if this instance was desummoned by being released rather than being digested. + wasReleased BOOLEAN DEFAULT NULL, + --- The unit's current health. If 0, the unit is unconscious and cannot participate in fights. + --- At -MaxHealth, the unit has been fully digested and this record will be deleted. + currentHealth INT NOT NULL, + --- The unit's maximum health. + maxHealth INT NOT NULL CHECK (maxHealth > 0), + --- The unit's strength. + strength INT NOT NULL CHECK (strength > 0), + --- The unit's current health must be between maxHealth and -maxHealth (the latter of which means digestion). + CONSTRAINT SummonedUnit_CurrentHealthBounds CHECK ( + currentHealth BETWEEN -maxHealth AND maxHealth), + --- Exactly one of wasPulled or wasRecalled must be TRUE. + CONSTRAINT SummonedUnit_ExactlyOneOrigin CHECK ( + ((wasPulled IS TRUE OR wasRecalled IS TRUE) AND NOT (wasPulled IS FALSE AND wasRecalled IS FALSE))), + --- Exactly one of wasDigested or wasReleased must be TRUE if desummonedAt is set, + --- and both must be NULL if desummonedAt is NULL. + CONSTRAINT SummonedUnit_ExactlyOneFate CHECK ( + ((wasDigested IS TRUE OR wasReleased IS TRUE) = (desummonedAt IS NOT NULL)) + AND (wasDigested IS NULL OR wasReleased IS NULL) = (desummonedAt IS NULL)), + --- The digesting summoned unit's instance ID must be set only if wasDigested is TRUE. + CONSTRAINT SummonedUnit_DigesterForDigestedOnly CHECK ( + digestedByInstanceId IS NULL OR wasDigested IS TRUE) +); + +--- No more than one instance of a particular unit may be summoned by the same player. +--- Once the previous instance has been desummoned (by any method), the unit may be summoned again. +CREATE UNIQUE INDEX IF NOT EXISTS SummonedUnit_OneInstancePerUnitPerPlayer + ON SummonedUnit (playerId, unitId) + WHERE (desummonedAt IS NULL); diff --git a/migrations/committed/000008-all-the-procedures.sql b/migrations/committed/000008-all-the-procedures.sql new file mode 100644 index 0000000..0af2331 --- /dev/null +++ b/migrations/committed/000008-all-the-procedures.sql @@ -0,0 +1,367 @@ +--! Previous: sha1:8a890a4c8807eb5a7aae90456ba1e7712cda502b +--! Hash: sha1:d45df95e7f3a77f0a9f7a8bfc6a93cfbebc1c61e +--! Message: all the procedures + +-- Create indices needed for ChannelManager commands. +CREATE INDEX IF NOT EXISTS DiscordChannel_GuildId ON DiscordChannel (guildId) WHERE guildId IS NOT NULL; +CREATE INDEX IF NOT EXISTS DiscordChannel_AcceptsGameCommands ON DiscordChannel (acceptGameCommands) WHERE acceptGameCommands IS TRUE; +CREATE INDEX IF NOT EXISTS DiscordChannel_AcceptsAdminCommands ON DiscordChannel (acceptAdminCommands) WHERE acceptAdminCommands IS TRUE; + +ALTER TABLE DiscordUser + ADD COLUMN IF NOT EXISTS lastActive TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(); + +--- Gets guild IDs where game commands can be used and thus guild commands must be pushed +CREATE OR REPLACE FUNCTION GetGuildIDsAbleToUseGameCommands() + RETURNS SETOF DiscordChannel.GuildId%TYPE + STABLE + ROWS 1 +AS +$$ +SELECT DISTINCT guildId +FROM DiscordChannel +WHERE guildId IS NOT NULL + AND acceptGameCommands IS TRUE; +$$ + LANGUAGE 'sql'; + +--- Gets whether the users may use game commands in the current channel/guild. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +CREATE OR REPLACE PROCEDURE CheckGameCommandIn( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE) +AS +$$ +DECLARE + channelAcceptsGameCommands BOOLEAN; + channelAcceptsAdminCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsGameCommands, channelAcceptsAdminCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsGameCommands IS NOT TRUE THEN + SELECT discordId, acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsGameCommands, guildSupportsAdminCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptGameCommands THEN 3 + WHEN acceptAdminCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsGameCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use game commands in this channel' USING + ERRCODE = 'VGBCG', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%>) is unused.', requestedChannel) + ELSE format('This channel (<#%>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use game commands in this guild' USING + ERRCODE = 'VGBGG', + DETAIL = CASE TRUE + WHEN guildSupportsAdminCommands THEN + format('This guild (ID %) only has channels used to send admin commands, ' || + 'not game commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %) is not known to the system.', requestedGuild) + END, + HINT = 'As game commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$ LANGUAGE 'plpgsql'; + +--- Gets guild IDs where admin commands can be used and thus guild commands must be pushed +CREATE OR REPLACE FUNCTION GetGuildIDsAbleToUseAdminCommands() + RETURNS SETOF DiscordChannel.GuildId%TYPE + STABLE + ROWS 1 +AS +$$ +SELECT DISTINCT guildId +FROM DiscordChannel +WHERE guildId IS NOT NULL + AND acceptAdminCommands IS TRUE; +$$ + LANGUAGE 'sql'; + +--- Gets whether the users may use admin commands in the current channel/guild. +--- Error codes: +--- VGBCA: Bad channel (admin). This is not a valid channel to send admin commands in. +--- VGBGA: Bad guild (admin). This is not a valid guild to send admin commands in. +CREATE OR REPLACE PROCEDURE CheckAdminCommandIn( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE) +AS +$$ +DECLARE + channelAcceptsAdminCommands BOOLEAN; + channelAcceptsGameCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsAdminCommands, channelAcceptsGameCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsAdminCommands IS NOT TRUE THEN + SELECT discordId, acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsAdminCommands, guildSupportsGameCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptAdminCommands THEN 3 + WHEN acceptGameCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsAdminCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use admin commands in this channel' USING + ERRCODE = 'VGBCA', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%>) is unused.', requestedChannel) + ELSE format('This channel (<#%>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use admin commands in this guild' USING + ERRCODE = 'VGBGA', + DETAIL = CASE TRUE + WHEN guildSupportsGameCommands THEN + format('This guild (ID %) only has channels used to send game commands, ' || + 'not admin commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %) is not known to the system.', requestedGuild) + END, + HINT = 'As admin commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$ LANGUAGE 'plpgsql'; + +--- Gets the list of genders that can be used to register and their corresponding IDs. +CREATE OR REPLACE FUNCTION GetRegisterableGenders( + OUT id Gender.id%TYPE, + OUT name Gender.name%TYPE) + RETURNS SETOF RECORD + STRICT + STABLE +AS +$$ +SELECT Gender.id, Gender.name +FROM Gender +$$ + LANGUAGE 'sql'; + +--- Updates a Discord user's username and discriminator, as well as their last active timestamp. +--- Creates the DiscordUser if they weren't previously recorded in the system. +CREATE OR REPLACE FUNCTION GetInvokingDiscordUser( + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE) + RETURNS DiscordUser + STRICT + STABLE +AS +$$ +INSERT INTO DiscordUser (discordId, username, discriminator, lastActive) +VALUES (forId, newUsername, newDiscriminator, NOW()) +ON CONFLICT (discordId) DO UPDATE SET username = newUsername, + discriminator = newDiscriminator, + lastActive = NOW() +RETURNING * +$$ + LANGUAGE 'sql'; + +--- Gets (and updates the lastActive timestamps of) the player corresponding to the Discord user given by ID. +--- If such a player does not exist, NULL will be returned and changes will only be made to the DiscordUser table. +CREATE OR REPLACE FUNCTION GetInvokingPlayer( + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE) + RETURNS Player + STRICT + STABLE +AS +$$ +UPDATE Player +SET lastActive = NOW() +FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator) AS InvokingDiscordUser +WHERE id = InvokingDiscordUser.playerId +RETURNING ROW (Player.*) +$$ LANGUAGE 'sql'; + +--- Adds a new player, or updates the existing player's name and gender. +CREATE OR REPLACE FUNCTION UpdatePlayerRegistration( + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN newPlayerName Player.name%TYPE, + IN newGenderId Gender.id%TYPE, + OUT resultId Player.id%TYPE, + OUT wasCreated BOOLEAN) + RETURNS RECORD + STRICT + STABLE +AS +$$ +DECLARE + playerId Player.id%TYPE; +BEGIN + SELECT InvokingDiscordUser.playerId + INTO STRICT playerId + FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator) + AS InvokingDiscordUser; + IF playerId IS NOT NULL THEN + wasCreated = FALSE; + UPDATE Player + SET name = newPlayerName, + genderId = newGenderId, + lastActive = NOW() + WHERE id = playerId + RETURNING id INTO resultId; + ELSE + wasCreated = TRUE; + INSERT INTO Player (name, genderId, currency, joinedAt, lastActive) + VALUES (newPlayerName, newGenderId, 100, NOW(), NOW()) + RETURNING id INTO resultId; + END IF; +END; +$$ + LANGUAGE 'plpgsql'; + +--- Removes the link between a DiscordUser and their Player. +--- Returns the previous player if one existed, or NULL if not. +CREATE OR REPLACE FUNCTION UnlinkDiscordUserFromPlayer( + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE) + RETURNS Player.id%TYPE + STRICT + STABLE +AS +$$ +DECLARE + oldPlayerId Player.id%TYPE = NULL; +BEGIN + SELECT playerId + INTO oldPlayerId + FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator); + + IF oldPlayerId IS NULL THEN + RETURN NULL; + END IF; + + UPDATE DiscordUser + SET playerId = NULL + WHERE discordId = forId; + + RETURN oldPlayerId; +END; + +$$ + LANGUAGE 'plpgsql'; + +--- Runs the full /join command. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +CREATE OR REPLACE FUNCTION Command_Join( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN newPlayerName Player.name%TYPE, + IN newGenderId Gender.id%TYPE, + OUT resultId Player.id%TYPE, + OUT newPlayerName Player.name%TYPE, + OUT newGenderName Gender.name%TYPE, + OUT wasCreated BOOLEAN +) + RETURNS RECORD + STRICT + STABLE +AS +$$ +CALL CheckGameCommandIn(requestedChannel, requestedGuild); + +SELECT NewRegistration.resultId, Player.name, Gender.name, NewRegistration.wasCreated +FROM UpdatePlayerRegistration(forId, newUsername, newDiscriminator, newPlayerName, newGenderId) AS NewRegistration + INNER JOIN Player ON Player.id = NewRegistration.resultId + INNER JOIN Gender ON Gender.id = Player.genderId +$$ + LANGUAGE 'sql'; + +--- Runs the full /unjoin command. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +CREATE OR REPLACE FUNCTION Command_Unjoin( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE +) + RETURNS Player.id%TYPE + STRICT + STABLE +AS +$$ +CALL CheckGameCommandIn(requestedChannel, requestedGuild); + +SELECT UnlinkDiscordUserFromPlayer(forId, newUsername, newDiscriminator) +$$ + LANGUAGE 'sql'; diff --git a/migrations/committed/000009-fix-webhooktoken-size.sql b/migrations/committed/000009-fix-webhooktoken-size.sql new file mode 100644 index 0000000..f104f96 --- /dev/null +++ b/migrations/committed/000009-fix-webhooktoken-size.sql @@ -0,0 +1,6 @@ +--! Previous: sha1:d45df95e7f3a77f0a9f7a8bfc6a93cfbebc1c61e +--! Hash: sha1:c1cb70e8c0dfc171d8f21abb14f503e47fb891bc +--! Message: fix webhookToken size + +ALTER TABLE DiscordChannel + ALTER COLUMN webhookToken TYPE VARCHAR(128); diff --git a/migrations/committed/000010-fix-function-volatility.sql b/migrations/committed/000010-fix-function-volatility.sql new file mode 100644 index 0000000..a62c4e6 --- /dev/null +++ b/migrations/committed/000010-fix-function-volatility.sql @@ -0,0 +1,10 @@ +--! Previous: sha1:c1cb70e8c0dfc171d8f21abb14f503e47fb891bc +--! Hash: sha1:c549ad6cc4dc88d928043916e2696eeed3efa420 +--! Message: fix function volatility + +ALTER FUNCTION Command_Join(varchar, varchar, varchar, varchar, varchar, varchar, varchar) VOLATILE; +ALTER FUNCTION Command_Unjoin(varchar, varchar, varchar, varchar, varchar) VOLATILE; +ALTER FUNCTION GetInvokingDiscordUser(varchar, varchar, varchar) VOLATILE; +ALTER FUNCTION GetInvokingPlayer(varchar, varchar, varchar) VOLATILE; +ALTER FUNCTION UpdatePlayerRegistration(varchar, varchar, varchar, varchar, varchar) VOLATILE; +ALTER FUNCTION UnlinkDiscordUserFromPlayer(varchar, varchar, varchar) VOLATILE; diff --git a/migrations/committed/000011-fix-some-more-procedures.sql b/migrations/committed/000011-fix-some-more-procedures.sql new file mode 100644 index 0000000..db0b1df --- /dev/null +++ b/migrations/committed/000011-fix-some-more-procedures.sql @@ -0,0 +1,197 @@ +--! Previous: sha1:c549ad6cc4dc88d928043916e2696eeed3efa420 +--! Hash: sha1:defb8b93ce936f1bb553abb90aaa6ecd5a00b1bc +--! Message: Fix some more procedures + +--- Gets whether the users may use game commands in the current channel/guild. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +CREATE OR REPLACE PROCEDURE CheckGameCommandIn( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE) +AS +$$ +DECLARE + channelAcceptsGameCommands BOOLEAN; + channelAcceptsAdminCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsGameCommands, channelAcceptsAdminCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsGameCommands IS NOT TRUE THEN + SELECT discordId, acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsGameCommands, guildSupportsAdminCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptGameCommands THEN 3 + WHEN acceptAdminCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsGameCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use game commands in this channel' USING + ERRCODE = 'VGBCG', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%s>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%s>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%s>) is unused.', requestedChannel) + ELSE format('This channel (<#%s>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%s> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use game commands in this guild' USING + ERRCODE = 'VGBGG', + DETAIL = CASE TRUE + WHEN guildSupportsAdminCommands THEN + format('This guild (ID %) only has channels used to send admin commands, ' || + 'not game commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %) is not known to the system.', requestedGuild) + END, + HINT = 'As game commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$ LANGUAGE 'plpgsql'; + +--- Gets whether the users may use admin commands in the current channel/guild. +--- Error codes: +--- VGBCA: Bad channel (admin). This is not a valid channel to send admin commands in. +--- VGBGA: Bad guild (admin). This is not a valid guild to send admin commands in. +CREATE OR REPLACE PROCEDURE CheckAdminCommandIn( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE) +AS +$$ +DECLARE + channelAcceptsAdminCommands BOOLEAN; + channelAcceptsGameCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsAdminCommands, channelAcceptsGameCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsAdminCommands IS NOT TRUE THEN + SELECT discordId, acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsAdminCommands, guildSupportsGameCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptAdminCommands THEN 3 + WHEN acceptGameCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsAdminCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use admin commands in this channel' USING + ERRCODE = 'VGBCA', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%s>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%s>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%s>) is unused.', requestedChannel) + ELSE format('This channel (<#%s>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%s> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use admin commands in this guild' USING + ERRCODE = 'VGBGA', + DETAIL = CASE TRUE + WHEN guildSupportsGameCommands THEN + format('This guild (ID %s) only has channels used to send game commands, ' || + 'not admin commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %s) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %s) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %s) is not known to the system.', requestedGuild) + END, + HINT = 'As admin commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$ LANGUAGE 'plpgsql'; + +--- Adds a new player, or updates the existing player's name and gender. +CREATE OR REPLACE FUNCTION UpdatePlayerRegistration( + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN newPlayerName Player.name%TYPE, + IN newGenderId Gender.id%TYPE, + OUT resultId Player.id%TYPE, + OUT wasCreated BOOLEAN) + RETURNS RECORD + STRICT + VOLATILE +AS +$$ +DECLARE + playerId Player.id%TYPE; +BEGIN + SELECT InvokingDiscordUser.playerId + INTO STRICT playerId + FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator) + AS InvokingDiscordUser; + IF playerId IS NOT NULL THEN + wasCreated = FALSE; + UPDATE Player + SET name = newPlayerName, + genderId = newGenderId, + lastActive = NOW() + WHERE id = playerId + RETURNING id INTO resultId; + ELSE + wasCreated = TRUE; + INSERT INTO Player (name, genderId, currency, joinedAt, lastActive) + VALUES (newPlayerName, newGenderId, 100, NOW(), NOW()) + RETURNING id INTO resultId; + UPDATE DiscordUser + SET playerId = resultId + WHERE discordId = forId; + END IF; +END; +$$ + LANGUAGE 'plpgsql'; diff --git a/migrations/committed/000012-fix-command_join-again.sql b/migrations/committed/000012-fix-command_join-again.sql new file mode 100644 index 0000000..0814432 --- /dev/null +++ b/migrations/committed/000012-fix-command_join-again.sql @@ -0,0 +1,33 @@ +--! Previous: sha1:defb8b93ce936f1bb553abb90aaa6ecd5a00b1bc +--! Hash: sha1:00899815dc03fa792300ff69dde9d17756448635 +--! Message: Fix Command_Join again + +--- Runs the full /join command. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +CREATE OR REPLACE FUNCTION Command_Join( + IN requestedChannel DiscordChannel.DiscordId%TYPE, + IN requestedGuild DiscordChannel.GuildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN newPlayerName Player.name%TYPE, + IN newGenderId Gender.id%TYPE, + OUT resultId Player.id%TYPE, + OUT newPlayerName Player.name%TYPE, + OUT newGenderName Gender.name%TYPE, + OUT wasCreated BOOLEAN +) + RETURNS RECORD + STRICT + VOLATILE +AS +$$ +CALL CheckGameCommandIn(requestedChannel, requestedGuild); + +SELECT NewRegistration.resultId, newPlayerName, Gender.name, NewRegistration.wasCreated +FROM UpdatePlayerRegistration(forId, newUsername, newDiscriminator, newPlayerName, newGenderId) AS NewRegistration + LEFT JOIN Gender ON Gender.id = newGenderId +$$ + LANGUAGE 'sql'; diff --git a/migrations/committed/000013-pull-command-skeleton.sql b/migrations/committed/000013-pull-command-skeleton.sql new file mode 100644 index 0000000..c9ffc82 --- /dev/null +++ b/migrations/committed/000013-pull-command-skeleton.sql @@ -0,0 +1,66 @@ +--! Previous: sha1:00899815dc03fa792300ff69dde9d17756448635 +--! Hash: sha1:e7ff9a64813ea9e0a701219f6b2ba8ed88dcf240 +--! Message: pull command skeleton + +--- Runs the full /pull command. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +--- VGNYJ: Not yet joined. The Discord user using has not joined the game yet. +--- VGNEC: Not enough currency. +CREATE OR REPLACE FUNCTION Command_Pull( + IN requestedChannel DiscordChannel.discordId%TYPE, + IN requestedGuild DiscordChannel.guildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN count INT +) + RETURNS TABLE + ( + summonedUnitInstanceId SummonedUnit.instanceId%TYPE, + summonedUnitId Unit.id%TYPE, + summonedUnitName Unit.name%TYPE, + summonedUnitSubtitle Unit.subtitle%TYPE, + summonedUnitTierName UnitTier.name%TYPE, + firstTimePull BOOLEAN, + wasAlreadySummoned BOOLEAN + ) + STRICT + VOLATILE + ROWS 10 +AS +$$ +DECLARE + playerId Player.id%TYPE; + cost Player.currency%TYPE; + oldCurrency Player.currency%TYPE; + playerLastDaily Player.lastDaily%TYPE; +BEGIN + CALL CheckGameCommandIn(requestedChannel, requestedGuild); + SELECT InvokingPlayer.id, InvokingPlayer.currency, InvokingPlayer.lastDaily + INTO playerId, oldCurrency, playerLastDaily + FROM GetInvokingPlayer(forId, newUsername, newDiscriminator) AS InvokingPlayer; + IF playerId IS NULL THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNYJ', + MESSAGE = 'Not yet joined', + DETAIL = 'You haven''t joined the game yet, and can''t use this command until you do.', + HINT = 'Use the /join command to join the game!'; + END IF; + cost = 10 * count; + UPDATE Player SET currency = currency - cost WHERE id = playerId AND currency >= cost; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNEC', + MESSAGE = 'Not enough currency', + DETAIL = format('Pulling %s heroines would cost %s currency, but you only have %s currency.', + count, cost, oldCurrency), + HINT = CASE playerLastDaily IS NULL OR playerLastDaily < NOW() - '1 day'::interval + WHEN TRUE THEN 'Try using the /daily command to get some more currency for today!' + ELSE format('Wait %s and you can use the /daily command to get some more currency!', + (playerLastDaily + '1 day'::interval) - NOW()) + END; + END IF; +END; +$$ LANGUAGE 'plpgsql'; diff --git a/migrations/dump.sql b/migrations/dump.sql new file mode 100644 index 0000000..d100713 --- /dev/null +++ b/migrations/dump.sql @@ -0,0 +1,542 @@ +create table if not exists discordchannel +( + discordid varchar(20) not null + constraint discordchannel_pkey + primary key, + name varchar(100) not null, + broadcastgame boolean default false not null, + sendlogs boolean default false not null, + acceptgamecommands boolean default false not null, + acceptadmincommands boolean default false not null, + priority smallint default 0 not null, + guildid varchar(20) default NULL::character varying, + webhookid varchar(20) default NULL::character varying, + webhooktoken varchar(128) default NULL::character varying, + constraint discordchannel_webhookpair + check (((webhookid IS NULL) AND (webhooktoken IS NULL)) OR + ((webhookid IS NOT NULL) AND (webhooktoken IS NOT NULL))) +); + +alter table discordchannel + owner to gacha; + +create index if not exists discordchannel_guildid + on discordchannel (guildid) + where (guildid IS NOT NULL); + +create index if not exists discordchannel_acceptsgamecommands + on discordchannel (acceptgamecommands) + where (acceptgamecommands IS TRUE); + +create index if not exists discordchannel_acceptsadmincommands + on discordchannel (acceptadmincommands) + where (acceptadmincommands IS TRUE); + +create table if not exists gender +( + id varchar(8) not null + constraint gender_pkey + primary key, + name varchar(100) not null + constraint gender_name_key + unique +); + +alter table gender + owner to gacha; + +create table if not exists player +( + id serial + constraint player_pkey + primary key, + name varchar(100) not null, + genderid varchar(8) not null + constraint player_genderid_fkey + references gender + on update cascade on delete restrict, + currency integer not null, + joinedat timestamp with time zone default now() not null, + lastactive timestamp with time zone default now() not null, + lastdaily timestamp with time zone +); + +alter table player + owner to gacha; + +create table if not exists discorduser +( + discordid varchar(20) not null + constraint discorduser_pkey + primary key, + username varchar(32) not null, + discriminator varchar(4) not null, + playerid integer + constraint discorduser_playerid_fkey + references player + on update cascade on delete set null, + lastactive timestamp with time zone default now() not null +); + +alter table discorduser + owner to gacha; + +create table if not exists unittier +( + id varchar(8) not null + constraint unittier_pkey + primary key, + name varchar(100) not null + constraint unittier_name_key + unique, + pullweight integer not null, + recallcost integer not null +); + +alter table unittier + owner to gacha; + +create table if not exists unit +( + id serial + constraint unit_pkey + primary key, + name varchar(50) not null, + subtitle varchar(50) not null, + description text not null, + tierid varchar(8) not null + constraint unit_tierid_fkey + references unittier + on update cascade on delete restrict, + basehealth integer not null + constraint unit_basehealth_check + check (basehealth > 0), + basestrength integer not null + constraint unit_basestrength_check + check (basestrength > 0), + constraint unit_name_subtitle_key + unique (name, subtitle) +); + +alter table unit + owner to gacha; + +create table if not exists summonedunit +( + instanceid serial + constraint summonedunit_pkey + primary key, + playerid integer not null + constraint summonedunit_playerid_fkey + references player + on update cascade on delete cascade, + unitid integer not null + constraint summonedunit_unitid_fkey + references unit + on update cascade on delete cascade, + summonedat timestamp with time zone default now() not null, + waspulled boolean default false not null, + wasrecalled boolean default false not null, + resummonings timestamp with time zone[], + desummonedat timestamp with time zone, + digestedbyinstanceid integer + constraint summonedunit_digestedbyinstanceid_fkey + references summonedunit + on update cascade on delete set null, + wasdigested boolean, + wasreleased boolean, + currenthealth integer not null, + maxhealth integer not null + constraint summonedunit_maxhealth_check + check (maxhealth > 0), + strength integer not null + constraint summonedunit_strength_check + check (strength > 0), + constraint summonedunit_currenthealthbounds + check ((currenthealth >= (- maxhealth)) AND (currenthealth <= maxhealth)), + constraint summonedunit_exactlyoneorigin + check (((waspulled IS TRUE) OR (wasrecalled IS TRUE)) AND + (NOT ((waspulled IS FALSE) AND (wasrecalled IS FALSE)))), + constraint summonedunit_exactlyonefate + check ((((wasdigested IS TRUE) OR (wasreleased IS TRUE)) = (desummonedat IS NOT NULL)) AND + (((wasdigested IS NULL) OR (wasreleased IS NULL)) = (desummonedat IS NULL))), + constraint summonedunit_digesterfordigestedonly + check ((digestedbyinstanceid IS NULL) OR (wasdigested IS TRUE)) +); + +alter table summonedunit + owner to gacha; + +create unique index if not exists summonedunit_oneinstanceperunitperplayer + on summonedunit (playerid, unitid) + where (desummonedat IS NULL); + +create or replace function getguildidsabletousegamecommands() returns SETOF character varying + stable + rows 1 + language sql +as +$$ +SELECT DISTINCT guildId +FROM DiscordChannel +WHERE guildId IS NOT NULL + AND acceptGameCommands IS TRUE; +$$; + +alter function getguildidsabletousegamecommands() owner to gacha; + +create or replace procedure checkgamecommandin(requestedchannel character varying, requestedguild character varying) + language plpgsql +as +$$ +DECLARE + channelAcceptsGameCommands BOOLEAN; + channelAcceptsAdminCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsGameCommands, channelAcceptsAdminCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsGameCommands IS NOT TRUE THEN + SELECT discordId, acceptGameCommands, acceptAdminCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsGameCommands, guildSupportsAdminCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptGameCommands THEN 3 + WHEN acceptAdminCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsGameCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use game commands in this channel' USING + ERRCODE = 'VGBCG', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%s>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%s>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%s>) is unused.', requestedChannel) + ELSE format('This channel (<#%s>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%s> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use game commands in this guild' USING + ERRCODE = 'VGBGG', + DETAIL = CASE TRUE + WHEN guildSupportsAdminCommands THEN + format('This guild (ID %) only has channels used to send admin commands, ' || + 'not game commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %) is not known to the system.', requestedGuild) + END, + HINT = 'As game commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$; + +alter procedure checkgamecommandin(varchar, varchar) owner to gacha; + +create or replace function getguildidsabletouseadmincommands() returns SETOF character varying + stable + rows 1 + language sql +as +$$ +SELECT DISTINCT guildId +FROM DiscordChannel +WHERE guildId IS NOT NULL + AND acceptAdminCommands IS TRUE; +$$; + +alter function getguildidsabletouseadmincommands() owner to gacha; + +create or replace procedure checkadmincommandin(requestedchannel character varying, requestedguild character varying) + language plpgsql +as +$$ +DECLARE + channelAcceptsAdminCommands BOOLEAN; + channelAcceptsGameCommands BOOLEAN; + channelSendsMessages BOOLEAN; + channelIsKnown BOOLEAN; + guildSupportsAdminCommands BOOLEAN; + guildSupportsGameCommands BOOLEAN; + guildSupportsMessages BOOLEAN; + guildIsKnown BOOLEAN; + recommendedChannelId DiscordChannel.DiscordId%TYPE; +BEGIN + SELECT acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO channelAcceptsAdminCommands, channelAcceptsGameCommands, channelSendsMessages, channelIsKnown + FROM DiscordChannel + WHERE discordId = requestedChannel + LIMIT 1; + IF channelAcceptsAdminCommands IS NOT TRUE THEN + SELECT discordId, acceptAdminCommands, acceptGameCommands, broadcastGame OR sendLogs, TRUE + INTO recommendedChannelId, guildSupportsAdminCommands, guildSupportsGameCommands, guildSupportsMessages, guildIsKnown + FROM DiscordChannel + WHERE guildId = requestedGuild + ORDER BY CASE TRUE + WHEN acceptAdminCommands THEN 3 + WHEN acceptGameCommands THEN 2 + WHEN broadcastGame OR sendLogs THEN 1 + ELSE 0 + END DESC, priority DESC + LIMIT 1; + IF guildSupportsAdminCommands IS TRUE THEN + RAISE EXCEPTION 'Can''t use admin commands in this channel' USING + ERRCODE = 'VGBCA', + DETAIL = CASE TRUE + WHEN channelAcceptsGameCommands THEN format( + 'This channel (<#%s>) can only be used to send admin commands, not game commands.', + requestedChannel) + WHEN channelSendsMessages THEN format( + 'This channel (<#%s>) is only used to receive broadcasts, not send commands.', + requestedChannel) + WHEN channelIsKnown THEN format( + 'This channel (<#%s>) is unused.', requestedChannel) + ELSE format('This channel (<#%s>) is not known to the system.', requestedChannel) + END, + HINT = format( + 'Try sending messages to the channel <#%s> in this guild, which does allow game commands.', + recommendedChannelId); + ELSE + RAISE EXCEPTION 'Can''t use admin commands in this guild' USING + ERRCODE = 'VGBGA', + DETAIL = CASE TRUE + WHEN guildSupportsGameCommands THEN + format('This guild (ID %s) only has channels used to send game commands, ' || + 'not admin commands.', requestedGuild) + WHEN guildSupportsMessages THEN + format('This guild (ID %s) only has channels used to receive broadcasts, ' || + 'not send commands.', requestedGuild) + WHEN guildIsKnown THEN + format('This guild (ID %s) only has unused channels.', requestedGuild) + ELSE format('This guild (ID %s) is not known to the system.', requestedGuild) + END, + HINT = 'As admin commands are normally only visible when a guild allows them, ' || + 'this guild may have been removed from the system incorrectly. ' || + 'Ask an admin to check what''s going on.'; + END IF; + END IF; +END; +$$; + +alter procedure checkadmincommandin(varchar, varchar) owner to gacha; + +create or replace function getregisterablegenders(OUT id character varying, OUT name character varying) returns SETOF record + stable + strict + language sql +as +$$ +SELECT Gender.id, Gender.name +FROM Gender +$$; + +alter function getregisterablegenders(out varchar, out varchar) owner to gacha; + +create or replace function getinvokingdiscorduser(forid character varying, newusername character varying, + newdiscriminator character varying) returns discorduser + strict + language sql +as +$$ +INSERT INTO DiscordUser (discordId, username, discriminator, lastActive) +VALUES (forId, newUsername, newDiscriminator, NOW()) +ON CONFLICT (discordId) DO UPDATE SET username = newUsername, + discriminator = newDiscriminator, + lastActive = NOW() +RETURNING * +$$; + +alter function getinvokingdiscorduser(varchar, varchar, varchar) owner to gacha; + +create or replace function getinvokingplayer(forid character varying, newusername character varying, + newdiscriminator character varying) returns player + strict + language sql +as +$$ +UPDATE Player +SET lastActive = NOW() +FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator) AS InvokingDiscordUser +WHERE id = InvokingDiscordUser.playerId +RETURNING ROW (Player.*) +$$; + +alter function getinvokingplayer(varchar, varchar, varchar) owner to gacha; + +create or replace function updateplayerregistration(forid character varying, newusername character varying, + newdiscriminator character varying, newplayername character varying, + newgenderid character varying, OUT resultid integer, + OUT wascreated boolean) returns record + strict + language plpgsql +as +$$ +DECLARE + playerId Player.id%TYPE; +BEGIN + SELECT InvokingDiscordUser.playerId + INTO STRICT playerId + FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator) + AS InvokingDiscordUser; + IF playerId IS NOT NULL THEN + wasCreated = FALSE; + UPDATE Player + SET name = newPlayerName, + genderId = newGenderId, + lastActive = NOW() + WHERE id = playerId + RETURNING id INTO resultId; + ELSE + wasCreated = TRUE; + INSERT INTO Player (name, genderId, currency, joinedAt, lastActive) + VALUES (newPlayerName, newGenderId, 100, NOW(), NOW()) + RETURNING id INTO resultId; + UPDATE DiscordUser + SET playerId = resultId + WHERE discordId = forId; + END IF; +END; +$$; + +alter function updateplayerregistration(varchar, varchar, varchar, varchar, varchar, out integer, out boolean) owner to gacha; + +create or replace function unlinkdiscorduserfromplayer(forid character varying, newusername character varying, + newdiscriminator character varying) returns integer + strict + language plpgsql +as +$$ +DECLARE + oldPlayerId Player.id%TYPE = NULL; +BEGIN + SELECT playerId + INTO oldPlayerId + FROM GetInvokingDiscordUser(forId, newUsername, newDiscriminator); + + IF oldPlayerId IS NULL THEN + RETURN NULL; + END IF; + + UPDATE DiscordUser + SET playerId = NULL + WHERE discordId = forId; + + RETURN oldPlayerId; +END; + +$$; + +alter function unlinkdiscorduserfromplayer(varchar, varchar, varchar) owner to gacha; + +create or replace function command_join(requestedchannel character varying, requestedguild character varying, + forid character varying, newusername character varying, + newdiscriminator character varying, newplayername character varying, + newgenderid character varying, OUT resultid integer, + OUT newplayername character varying, OUT newgendername character varying, + OUT wascreated boolean) returns record + strict + language sql +as +$$ +CALL CheckGameCommandIn(requestedChannel, requestedGuild); + +SELECT NewRegistration.resultId, newPlayerName, Gender.name, NewRegistration.wasCreated +FROM UpdatePlayerRegistration(forId, newUsername, newDiscriminator, newPlayerName, newGenderId) AS NewRegistration + LEFT JOIN Gender ON Gender.id = newGenderId +$$; + +alter function command_join(varchar, varchar, varchar, varchar, varchar, varchar, varchar, out integer, out varchar, out varchar, out boolean) owner to gacha; + +create or replace function command_unjoin(requestedchannel character varying, requestedguild character varying, + forid character varying, newusername character varying, + newdiscriminator character varying) returns integer + strict + language sql +as +$$ +CALL CheckGameCommandIn(requestedChannel, requestedGuild); + +SELECT UnlinkDiscordUserFromPlayer(forId, newUsername, newDiscriminator) +$$; + +alter function command_unjoin(varchar, varchar, varchar, varchar, varchar) owner to gacha; + +create or replace function command_pull(requestedchannel character varying, requestedguild character varying, + forid character varying, newusername character varying, + newdiscriminator character varying, count integer) + returns TABLE + ( + summonedunitinstanceid integer, + summonedunitid integer, + summonedunitname character varying, + summonedunitsubtitle character varying, + summonedunittiername character varying, + firsttimepull boolean, + wasalreadysummoned boolean + ) + strict + rows 10 + language plpgsql +as +$$ +DECLARE + playerId Player.id%TYPE; + cost Player.currency%TYPE; + oldCurrency Player.currency%TYPE; + playerLastDaily Player.lastDaily%TYPE; +BEGIN + CALL CheckGameCommandIn(requestedChannel, requestedGuild); + SELECT InvokingPlayer.id, InvokingPlayer.currency, InvokingPlayer.lastDaily + INTO playerId, oldCurrency, playerLastDaily + FROM GetInvokingPlayer(forId, newUsername, newDiscriminator) AS InvokingPlayer; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNYJ', + MESSAGE = 'Not yet joined', + DETAIL = 'You haven''t joined the game yet, and can''t use this command until you do.', + HINT = 'Use the /join command to join the game!'; + END IF; + cost = 10 * count; + UPDATE Player SET currency = currency - cost WHERE id = playerId AND currency >= cost; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNEC', + MESSAGE = 'Not enough currency', + DETAIL = format('Pulling %s heroines would cost %s currency, but you only have %s currency.', + count, cost, oldCurrency), + HINT = CASE playerLastDaily IS NULL OR playerLastDaily < NOW() - '1 day'::interval + WHEN TRUE THEN 'Try using the /daily command to get some more currency for today!' + ELSE format('Wait %s and you can use the /daily command to get some more currency!', + (playerLastDaily + '1 day'::interval) - NOW()) + END; + END IF; +END; +$$; + +alter function command_pull(varchar, varchar, varchar, varchar, varchar, integer) owner to gacha; + diff --git a/package-lock.json b/package-lock.json index 9b4f2cd..6e58fab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,8 @@ "name": "vore-gacha", "version": "0.0.1", "dependencies": { - "@prisma/client": "^3.6.0", "axios": "^0.24.0", "chance": "^1.1.8", - "crypto-random-string": "^4.0.0", "cuid": "^2.1.8", "detritus-client": "^0.16.3", "detritus-client-rest": "^0.10.5", @@ -19,6 +17,7 @@ "dotenv": "^10.0.0", "fastify": "^3.24.1", "fastify-cookie": "^5.4.0", + "pg": "^8.7.1", "pino": "^7.5.1", "pino-discord": "^1.0.2", "pug": "^3.0.2", @@ -32,8 +31,9 @@ "@types/pug": "^2.0.5", "@types/relateurl": "^0.2.29", "@types/simple-oauth2": "^4.1.1", + "dotenv-cli": "^4.1.1", + "graphile-migrate": "^1.2.0", "pino-pretty": "^7.3.0", - "prisma": "^3.6.0", "typescript": "^4.5.3" } }, @@ -85,6 +85,12 @@ "ajv": "^6.12.6" } }, + "node_modules/@graphile/logger": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@graphile/logger/-/logger-0.2.0.tgz", + "integrity": "sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==", + "dev": true + }, "node_modules/@hapi/boom": { "version": "9.1.4", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", @@ -121,38 +127,6 @@ "@hapi/hoek": "9.x.x" } }, - "node_modules/@prisma/client": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.6.0.tgz", - "integrity": "sha512-ycSGY9EZGROtje0iCNsgC5Zqi/ttX2sO7BNMYaLsUMiTlf3F69ZPH+08pRo0hrDfkZzyimXYqeXJlaoYDH1w7A==", - "hasInstallScript": true, - "dependencies": { - "@prisma/engines-version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727" - }, - "engines": { - "node": ">=12.6" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/engines": { - "version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz", - "integrity": "sha512-dRClHS7DsTVchDKzeG72OaEyeDskCv91pnZ72Fftn0mp4BkUvX2LvWup65hCNzwwQm5IDd6A88APldKDnMiEMA==", - "devOptional": true, - "hasInstallScript": true - }, - "node_modules/@prisma/engines-version": { - "version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz", - "integrity": "sha512-vtoO2ys6mSfc8ONTWdcYztKN3GBU1tcKBj0aXObyjzSuGwHFcM/pEA0xF+n1W4/0TAJgfoPX2khNEit6g0jtNA==" - }, "node_modules/@sideway/address": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", @@ -177,6 +151,12 @@ "integrity": "sha512-X6c6ghhe4/sQh4XzcZWSFaTAUOda38GQHmq9BUanYkOE/EO7ZrkazwKmtsj3xzTjkLWmwULE++23g3d3CCWaWw==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", + "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==", + "dev": true + }, "node_modules/@types/node": { "version": "16.11.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", @@ -191,6 +171,17 @@ "form-data": "^3.0.0" } }, + "node_modules/@types/pg": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.3.tgz", + "integrity": "sha512-P0RrXJcw1hPS+KF0nBCC3FEEdZBfRsHbYtAzbY2QTc0gC4jFQvjQxIKXs0X/NgPhPI4DbAzdeW7WMCjaWjT7Pg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/pug": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.5.tgz", @@ -240,6 +231,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -252,6 +252,19 @@ "node": ">=4" } }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -325,6 +338,35 @@ "node": ">= 10.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -373,6 +415,38 @@ "is-regex": "^1.0.3" } }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -438,18 +512,18 @@ "node": ">=6.6.0" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { - "type-fest": "^1.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, "node_modules/cuid": { @@ -482,6 +556,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", @@ -626,6 +709,36 @@ "node": ">=10" } }, + "node_modules/dotenv-cli": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-4.1.1.tgz", + "integrity": "sha512-XvKv1pa+UBrsr3CtLGBsR6NdsoS7znqaHUf4Knj0eZO+gOI/hjj9KgWDP+KjpfEbj6wAba1UpbhaP9VezNkWhg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1", + "dotenv": "^8.1.0", + "dotenv-expand": "^5.1.0", + "minimist": "^1.1.3" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, "node_modules/duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -637,6 +750,12 @@ "stream-shift": "^1.0.0" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -786,6 +905,18 @@ "reusify": "^1.0.4" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-my-way": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.4.0.tgz", @@ -800,6 +931,19 @@ "node": ">=10" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", @@ -845,6 +989,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -871,6 +1029,114 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graphile-migrate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/graphile-migrate/-/graphile-migrate-1.2.0.tgz", + "integrity": "sha512-GFEdmULjo+HEucS8ECdCYsup7IHY/pP9KOiHLwmA949c4lZUdGbwP6XXJ5Cp1I2lRk4s/1BV1mh54GIK1CBljw==", + "dev": true, + "dependencies": { + "@graphile/logger": "^0.2.0", + "@types/json5": "^0.0.30", + "@types/node": "^14.6.0", + "@types/pg": ">=6 <9", + "chalk": "^3.0.0", + "chokidar": "^3.5.1", + "json5": "^2.1.2", + "pg": ">=6.5 <9", + "pg-connection-string": "^2.1.0", + "pg-minify": "^1.5.2", + "tslib": "^1.10.0", + "yargs": "^15.3.1" + }, + "bin": { + "graphile-migrate": "dist/cli.js" + } + }, + "node_modules/graphile-migrate/node_modules/@types/node": { + "version": "14.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.3.tgz", + "integrity": "sha512-GtTH2crF4MtOIrrAa+jgTV9JX/PfoUCYr6MiZw7O/dkZu5b6gm5dc1nAL0jwGo4ortSBBtGyeVaxdC8X6V+pLg==", + "dev": true + }, + "node_modules/graphile-migrate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/graphile-migrate/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graphile-migrate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/graphile-migrate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/graphile-migrate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/graphile-migrate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -929,6 +1195,18 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", @@ -949,6 +1227,45 @@ "object-assign": "^4.1.1" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -969,6 +1286,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "node_modules/joi": { "version": "17.5.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.5.0.tgz", @@ -1000,6 +1323,21 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -1049,6 +1387,18 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -1089,6 +1439,12 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "node_modules/mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -1114,6 +1470,15 @@ "node": "4.x || >=6.0.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1135,11 +1500,165 @@ "wrappy": "1" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-minify": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz", + "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==", + "dev": true, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pino": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/pino/-/pino-7.5.1.tgz", @@ -1220,21 +1739,39 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" }, - "node_modules/prisma": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.6.0.tgz", - "integrity": "sha512-6SqgHS/5Rq6HtHjsWsTxlj+ySamGyCLBUQfotc2lStOjPv52IQuDVpp58GieNqc9VnfuFyHUvTZw7aQB+G2fvQ==", - "devOptional": true, - "hasInstallScript": true, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "dependencies": { - "@prisma/engines": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727" - }, - "bin": { - "prisma": "build/index.js", - "prisma2": "build/index.js" + "xtend": "^4.0.0" }, "engines": { - "node": ">=12.6" + "node": ">=0.10.0" } }, "node_modules/promise": { @@ -1423,6 +1960,18 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/real-require": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz", @@ -1447,6 +1996,15 @@ "node": ">= 0.8" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1455,6 +2013,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -1548,11 +2112,38 @@ "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "node_modules/set-cookie-parser": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/simple-oauth2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/simple-oauth2/-/simple-oauth2-4.2.0.tgz", @@ -1626,6 +2217,32 @@ "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1682,6 +2299,18 @@ "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/token-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", @@ -1692,22 +2321,17 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, - "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz", @@ -1756,6 +2380,27 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "node_modules/with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -1770,6 +2415,53 @@ "node": ">= 10.0.0" } }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1794,11 +2486,60 @@ "optional": true } } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } } }, "dependencies": { @@ -1834,6 +2575,12 @@ "ajv": "^6.12.6" } }, + "@graphile/logger": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@graphile/logger/-/logger-0.2.0.tgz", + "integrity": "sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==", + "dev": true + }, "@hapi/boom": { "version": "9.1.4", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", @@ -1870,25 +2617,6 @@ "@hapi/hoek": "9.x.x" } }, - "@prisma/client": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.6.0.tgz", - "integrity": "sha512-ycSGY9EZGROtje0iCNsgC5Zqi/ttX2sO7BNMYaLsUMiTlf3F69ZPH+08pRo0hrDfkZzyimXYqeXJlaoYDH1w7A==", - "requires": { - "@prisma/engines-version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727" - } - }, - "@prisma/engines": { - "version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz", - "integrity": "sha512-dRClHS7DsTVchDKzeG72OaEyeDskCv91pnZ72Fftn0mp4BkUvX2LvWup65hCNzwwQm5IDd6A88APldKDnMiEMA==", - "devOptional": true - }, - "@prisma/engines-version": { - "version": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727.tgz", - "integrity": "sha512-vtoO2ys6mSfc8ONTWdcYztKN3GBU1tcKBj0aXObyjzSuGwHFcM/pEA0xF+n1W4/0TAJgfoPX2khNEit6g0jtNA==" - }, "@sideway/address": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", @@ -1913,6 +2641,12 @@ "integrity": "sha512-X6c6ghhe4/sQh4XzcZWSFaTAUOda38GQHmq9BUanYkOE/EO7ZrkazwKmtsj3xzTjkLWmwULE++23g3d3CCWaWw==", "dev": true }, + "@types/json5": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", + "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==", + "dev": true + }, "@types/node": { "version": "16.11.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", @@ -1927,6 +2661,17 @@ "form-data": "^3.0.0" } }, + "@types/pg": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.3.tgz", + "integrity": "sha512-P0RrXJcw1hPS+KF0nBCC3FEEdZBfRsHbYtAzbY2QTc0gC4jFQvjQxIKXs0X/NgPhPI4DbAzdeW7WMCjaWjT7Pg==", + "dev": true, + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "@types/pug": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.5.tgz", @@ -1966,6 +2711,12 @@ "uri-js": "^4.2.2" } }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1975,6 +2726,16 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -2039,6 +2800,26 @@ "@babel/types": "^7.9.6" } }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2078,6 +2859,33 @@ "is-regex": "^1.0.3" } }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2131,12 +2939,15 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.1.0.tgz", "integrity": "sha512-Alvs19Vgq07eunykd3Xy2jF0/qSNv2u7KDbAek9H5liV1UMijbqFs5cycZvv5dVsvseT/U4H8/7/w8Koh35C4A==" }, - "crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { - "type-fest": "^1.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "cuid": { @@ -2158,6 +2969,12 @@ "ms": "2.1.2" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", @@ -2245,6 +3062,32 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" }, + "dotenv-cli": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-4.1.1.tgz", + "integrity": "sha512-XvKv1pa+UBrsr3CtLGBsR6NdsoS7znqaHUf4Knj0eZO+gOI/hjj9KgWDP+KjpfEbj6wAba1UpbhaP9VezNkWhg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1", + "dotenv": "^8.1.0", + "dotenv-expand": "^5.1.0", + "minimist": "^1.1.3" + }, + "dependencies": { + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + } + } + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, "duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -2256,6 +3099,12 @@ "stream-shift": "^1.0.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2395,6 +3244,15 @@ "reusify": "^1.0.4" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-my-way": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.4.0.tgz", @@ -2406,6 +3264,16 @@ "semver-store": "^0.3.0" } }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", @@ -2431,6 +3299,13 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2451,6 +3326,92 @@ "has-symbols": "^1.0.1" } }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graphile-migrate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/graphile-migrate/-/graphile-migrate-1.2.0.tgz", + "integrity": "sha512-GFEdmULjo+HEucS8ECdCYsup7IHY/pP9KOiHLwmA949c4lZUdGbwP6XXJ5Cp1I2lRk4s/1BV1mh54GIK1CBljw==", + "dev": true, + "requires": { + "@graphile/logger": "^0.2.0", + "@types/json5": "^0.0.30", + "@types/node": "^14.6.0", + "@types/pg": ">=6 <9", + "chalk": "^3.0.0", + "chokidar": "^3.5.1", + "json5": "^2.1.2", + "pg": ">=6.5 <9", + "pg-connection-string": "^2.1.0", + "pg-minify": "^1.5.2", + "tslib": "^1.10.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "@types/node": { + "version": "14.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.3.tgz", + "integrity": "sha512-GtTH2crF4MtOIrrAa+jgTV9JX/PfoUCYr6MiZw7O/dkZu5b6gm5dc1nAL0jwGo4ortSBBtGyeVaxdC8X6V+pLg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2488,6 +3449,15 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-core-module": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", @@ -2505,6 +3475,33 @@ "object-assign": "^4.1.1" } }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -2519,6 +3516,12 @@ "has-tostringtag": "^1.0.0" } }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "joi": { "version": "17.5.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.5.0.tgz", @@ -2547,6 +3550,15 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -2591,6 +3603,15 @@ } } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -2622,6 +3643,12 @@ "mime-db": "1.51.0" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -2641,6 +3668,12 @@ "whatwg-url": "^5.0.0" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2659,11 +3692,119 @@ "wrappy": "1" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-minify": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz", + "integrity": "sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg==", + "dev": true + }, + "pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, "pino": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/pino/-/pino-7.5.1.tgz", @@ -2737,13 +3878,27 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" }, - "prisma": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.6.0.tgz", - "integrity": "sha512-6SqgHS/5Rq6HtHjsWsTxlj+ySamGyCLBUQfotc2lStOjPv52IQuDVpp58GieNqc9VnfuFyHUvTZw7aQB+G2fvQ==", - "devOptional": true, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { - "@prisma/engines": "3.6.0-24.dc520b92b1ebb2d28dc3161f9f82e875bd35d727" + "xtend": "^4.0.0" } }, "promise": { @@ -2909,6 +4064,15 @@ "util-deprecate": "^1.0.1" } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "real-require": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz", @@ -2924,11 +4088,23 @@ "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=" }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -2989,11 +4165,32 @@ "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "set-cookie-parser": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "simple-oauth2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/simple-oauth2/-/simple-oauth2-4.2.0.tgz", @@ -3049,6 +4246,26 @@ "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3090,6 +4307,15 @@ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "token-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", @@ -3100,16 +4326,17 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, - "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" - }, "typescript": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz", @@ -3148,6 +4375,21 @@ "webidl-conversions": "^3.0.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -3159,6 +4401,43 @@ "babel-walk": "3.0.0-canary-5" } }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3170,10 +4449,50 @@ "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", "requires": {} }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } } diff --git a/package.json b/package.json index 0306f0c..cd768d1 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,8 @@ "name": "vore-gacha", "version": "0.0.1", "dependencies": { - "@prisma/client": "^3.6.0", "axios": "^0.24.0", "chance": "^1.1.8", - "crypto-random-string": "^4.0.0", "cuid": "^2.1.8", "detritus-client": "^0.16.3", "detritus-client-rest": "^0.10.5", @@ -13,6 +11,7 @@ "dotenv": "^10.0.0", "fastify": "^3.24.1", "fastify-cookie": "^5.4.0", + "pg": "^8.7.1", "pino": "^7.5.1", "pino-discord": "^1.0.2", "pug": "^3.0.2", @@ -26,19 +25,22 @@ "@types/pug": "^2.0.5", "@types/relateurl": "^0.2.29", "@types/simple-oauth2": "^4.1.1", + "dotenv-cli": "^4.1.1", + "graphile-migrate": "^1.2.0", "pino-pretty": "^7.3.0", - "prisma": "^3.6.0", "typescript": "^4.5.3" }, - "type": "module", "scripts": { "build": "tsc --build", "clean": "rm -rf build generated", "start": "node build/app.js", - "regenerate": "prisma generate", + "dbReset": "dotenv -- graphile-migrate reset", + "dbMigrate": "dotenv -- graphile-migrate migrate", + "dbWatch": "dotenv -- graphile-migrate watch", + "dbCommit": "dotenv -- graphile-migrate commit --message", + "dbUncommit": "dotenv -- graphile-migrate uncommit", + "dbStep": "dotenv -- graphile-migrate watch --once", "loadWebhooks": "node build/tools/LoadJsonWebhooks.js", - "loadGenders": "node build/tools/LoadGenders.js", - "pushDb": "prisma db push --skip-generate", - "fullRebuild": "npm run clean && npm run regenerate && npm run pushDb && npm run build && npm run loadWebhooks && npm run loadGenders" + "fullRebuild": "npm run clean && npm run -- dbReset --erase && npm run build && npm run loadWebhooks" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index e4b38bf..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,185 +0,0 @@ -datasource db { - provider = "sqlite" - url = "file:../runtime/database/db.sqlite" -} - -generator client { - provider = "prisma-client-js" -} - -/// Discord channels known to the server. -model DiscordChannel { - /// The ID of the channel in Discord. - discordId String @id - /// The last known name of this channel. - name String - /// True if this channel should be used to broadcast public game events. - broadcastGame Boolean - /// True if this channel should be used to send logs. - sendLogs Boolean - /// True if this channel can accept game commands. - acceptGameCommands Boolean - /// True if this channel can accept admin commands. - acceptAdminCommands Boolean - /// The priority of this channel when slowing down to account for rate limits. Higher is more important. - priority Int - /// The guild in which this channel exists, if it's known. - guildId String? - /// The ID of the webhook used to post to this channel. Deleted if the webhook 404's. - webhookId String? - /// The webhook token used to post to this channel. Deleted if the webhook 404's. - token String? -} - -/// User genders. -model Gender { - /// The internal ID associated with this gender. - id String @id @default(cuid()) - /// The human-readable name of this gender. Unique among genders. - name String @unique - /// The users with this gender. - User User[] -} - -/// In-game user data structure. -model User { - /// The internal ID associated with this account. - /// It's separate from the Discord ID associated with this account. This supports a few things: - /// 1) We can move the game off of Discord, or add the ability to play it as a separate phone app or webapp, - /// without losing our database. - /// 2) If necessary, we can support having multiple Discord users associated with the same user account, to - /// support multi-account play. - /// 3) If necessary, we can support changing the Discord user associated with a user account, if for any reason - /// they are no longer using the old account and want to switch to a new account. - id String @id @default(cuid()) - /// The user's name, for the purposes of the game. This is completely separate from both their username and nickname - /// as Discord sees it, though it defaults to their nickname at time of joining. It does not have to be unique, and - /// can be changed at any time. - name String - /// The discord user associated with this account. - discordUser DiscordUser? - /// The user's gender, for the purposes of the game. This is purely cosmetic and can be changed at any time. - gender Gender @relation(fields: [genderId], references: [id]) - /// Relation field for Gender - genderId String - /// The number of units of currency this user is currently carrying. - currency Int @default(100) - /// The time and date at which this user joined. - joinedAt DateTime @default(now()) - /// The last time this user used a command. - lastActive DateTime @default(now()) - /// The last time this user retrieved their daily resources. - lastDaily DateTime? - /// List of units this user has ever seen/pulled. - units UserUnit[] - /// List of units currently in this user's party. - summonedUnits SummonedUnit[] -} - -/// Informmation about a Discord user. -model DiscordUser { - /// The Discord ID this record is for. A Discord snowflake. - discordId String @id - /// The last known username associated with this user. - username String - /// The last known discriminator associated with this user. - discriminator String - /// The User that this DiscordUser is associated with. - user User? @relation(fields: [userId], references: [id]) - /// Relation field for User - userId String? @unique -} - -/// Definitions of unit tiers. -model Tier { - /// The internal ID associated with this tier. - id String @id @default(cuid()) - /// The human-readable name of this tier. Unique among tiers. - name String @unique - /// The chance of pulling a unit of this tier. - pullWeight Int - /// The cost of /recalling a unit of this tier. - recallCost Int - /// The list of units with this tier. - units Unit[] -} - -/// An individual unit that can be summoned. -model Unit { - /// The combination of Name and Subtitle is unique among units, allowing for multiple versions of a unit. - /// The internal ID associated with this unit. - id String @id @default(cuid()) - /// The name of this unit. - name String - /// The subtitle of this unit. - subtitle String - /// The description of this unit. - description String - /// The tier of this unit. - tier Tier @relation(fields: [tierId], references: [id]) - /// Relation field for Tier - tierId String - /// The unit's base health when summoned for the first time. - baseHealth Int - /// The unit's base strength when summoned for the first time. - baseStrength Int - /// Information about the bonds with users this unit has been summoned by. - users UserUnit[] - /// Information about this unit's summoned forms. - summonedUnits SummonedUnit[] - - @@unique([name, subtitle]) -} - -/// Connection between Users and Units, indicating how and when users have pulled this unit. -model UserUnit { - /// The User that summoned this unit at some point. - user User @relation(fields: [userId], references: [id]) - /// Relation field for User - userId String - /// The Unit that was summoned by this User at some point. - unit Unit @relation(fields: [unitId], references: [id]) - /// Relation field for Unit - unitId String - /// The first time this user pulled this unit. - firstPulled DateTime @default(now()) - /// The number of times this unit has been /pulled by this user. - /// Greatly increases the user's bond with this unit. - /// Higher bond means higher stats on being resummoned with /pull or /recall. - timesPulled Int @default(1) - /// The number of times this unit has been digested in this user's party, either with /feed or in battle. - /// Slightly decreases the user's bond with this unit. - /// If the total bond reaches zero, this unit can no longer be resummoned with /recall until they appear in /pull. - timesDigested Int @default(0) - /// The number of times this unit has digested other units, either with /feed or in battle. - /// Does not influence bond, but may have an influence on other things (e.g., Talk lines). - timesDigesting Int @default(0) - /// The summoned form of this unit, if this unit is currently summoned. - /// Created on pulling or recalling, destroyed on digestion. - summonedUnit SummonedUnit? - - @@id([userId, unitId]) -} - -/// Instances of summoned units. -model SummonedUnit { - /// The User this unit was summoned by. - user User @relation(fields: [userId], references: [id]) - /// The Unit this summoned unit is an instance of. - unit Unit @relation(fields: [unitId], references: [id]) - /// The user-unit pair this SummonedUnit originates with. - userUnit UserUnit @relation(fields: [userId, unitId], references: [userId, unitId]) - /// Relation field for User and UserUnit - userId String - /// Relation field for Unit and UserUnit - unitId String - /// The unit's current health. If 0, the unit is unconscious and cannot participate in fights. - /// At -MaxHealth, the unit has been fully digested and this record will be deleted. - currentHealth Int - /// The unit's maximum health. - maxHealth Int - /// The unit's strength. - strength Int - - @@id([userId, unitId]) -} diff --git a/src/BaseServer.ts b/src/BaseServer.ts index d7e7fb1..cb26b40 100644 --- a/src/BaseServer.ts +++ b/src/BaseServer.ts @@ -2,7 +2,7 @@ import fastify, {FastifyInstance} from "fastify"; import {SlashCreator, SlashCreatorOptions} from "slash-create"; import fastifyCookie from "fastify-cookie"; import {FastifyServerButItWorksUnlikeTheRealOne} from "./FastifyHelpers.js"; -import {ChannelManager} from "./queries/ChannelManager.js"; +import {Pool} from "pg"; export interface BaseServerDeps { appId: string @@ -12,7 +12,7 @@ export interface BaseServerDeps { listenPort: number listenAddress: string cookieSecret: string - channelManager: ChannelManager + pool: Pool } export class BaseServer { @@ -22,7 +22,7 @@ export class BaseServer { readonly listenPort: number readonly listenAddress: string readonly slashcmd: SlashCreator - readonly channelManager: ChannelManager + readonly pool: Pool constructor({ cookieSecret, @@ -30,7 +30,7 @@ export class BaseServer { clientSecret, listenPort, listenAddress, - channelManager, + pool, publicKey, botToken, slashCreatorOptions = {} @@ -57,7 +57,7 @@ export class BaseServer { this.clientSecret = clientSecret this.listenPort = listenPort this.listenAddress = listenAddress - this.channelManager = channelManager + this.pool = pool } async _initInternal(): Promise { diff --git a/src/CookieHelpers.ts b/src/CookieHelpers.ts index d2d62ff..3ca005e 100644 --- a/src/CookieHelpers.ts +++ b/src/CookieHelpers.ts @@ -1,6 +1,6 @@ import {FastifyReply, FastifyRequest} from "fastify"; import {RouteGenericInterface} from "fastify/types/route"; -import cryptoRandomString from "crypto-random-string"; +import {randomBytes} from "crypto"; export interface XSRFRoute extends RouteGenericInterface { Querystring: { [key in typeof XSRFParameter]: string | string[] | undefined } @@ -9,11 +9,8 @@ export interface XSRFRoute extends RouteGenericInterface { export const XSRFCookie = "__Host-XSRF-Cookie"; export const XSRFParameter = "state" as const; -export function generateXSRFCookie(res: FastifyReply): string { - const newState = cryptoRandomString({ - length: 32, - type: 'url-safe' - }) +export async function generateXSRFCookie(res: FastifyReply): Promise { + const newState = randomBytes(30).toString("base64url") res.setCookie(XSRFCookie, newState, { path: "/", sameSite: "strict", diff --git a/src/GameServer.ts b/src/GameServer.ts index 7e7779b..cb9df67 100644 --- a/src/GameServer.ts +++ b/src/GameServer.ts @@ -2,34 +2,39 @@ import {checkAndClearXSRFCookie, generateXSRFCookie, XSRFRoute} from "./CookieHe import {renderError} from "./PugRenderer.js"; import {getBaseUrl} from "./FastifyHelpers.js"; import pug from "pug"; -import {BaseServer, BaseServerDeps} from "./BaseServer.js"; +import {BaseServer} from "./BaseServer.js"; import {PullCommand} from "./commands/game/PullCommand.js"; import {JoinCommand} from "./commands/game/JoinCommand.js"; -import {UserManager} from "./queries/UserManager.js"; +import {singleColumnQueryResult} from "./queries/QueryHelpers.js"; +import {UnjoinCommand} from "./commands/debug/UnjoinCommand.js"; export class GameServer extends BaseServer { - readonly userManager: UserManager - - constructor(deps: BaseServerDeps & { userManager: UserManager }) { - super(deps) - this.userManager = deps.userManager - } async _initInternal(): Promise { - const gameGuildIds = await this.channelManager.getGameCommandGuildIds() - const genders = await this.userManager.getGenders() + const promisedGameGuildIds = this.pool.query<[string]>({ + text: `SELECT * + FROM GetGuildIdsAbleToUseGameCommands()`, + rowMode: "array", + }) + const promisedGenders = this.pool.query<{ id: string, name: string }>({ + text: `SELECT id, name + FROM GetRegisterableGenders()`, + }) this.slashcmd.registerCommand(new PullCommand(this.slashcmd, { - channelManager: this.channelManager, - gameGuildIds, + pool: this.pool, + gameGuildIds: singleColumnQueryResult(await promisedGameGuildIds), })) this.slashcmd.registerCommand(new JoinCommand(this.slashcmd, { - channelManager: this.channelManager, - userManager: this.userManager, - gameGuildIds, - genders, + pool: this.pool, + gameGuildIds: singleColumnQueryResult(await promisedGameGuildIds), + genders: (await promisedGenders).rows, + })) + this.slashcmd.registerCommand(new UnjoinCommand(this.slashcmd, { + pool: this.pool, + gameGuildIds: singleColumnQueryResult(await promisedGameGuildIds), })) this.server.get("/game/started", async (req, res) => { - const token = generateXSRFCookie(res) + const token = await generateXSRFCookie(res) res.code(200) res.type("text/html") res.send(pug.renderFile("static/pages/game/running.pug", { diff --git a/src/app.ts b/src/app.ts index 87c08f2..5b8ccb9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,10 +1,7 @@ import dotenv from "dotenv"; import {GameServer} from "./GameServer.js"; -import cryptoRandomString from "crypto-random-string"; import pino from "pino" -import {PrismaClient} from "./queries/Prisma.js"; -import {ChannelManager} from "./queries/ChannelManager.js"; -import {UserManager} from "./queries/UserManager.js"; +import {Pool} from "pg"; const log = pino() @@ -19,17 +16,17 @@ async function main(): Promise { const publicKey = parsed["DISCORD_PUBLIC_KEY"] const listenPort = parseInt(parsed["HTTP_PORT"] ?? "5244") const listenAddress = parsed["HTTP_ADDRESS"] ?? "127.0.0.1" - const cookieSecret = parsed["COOKIE_SECRET"] ?? cryptoRandomString({length: 32, type: "base64"}) - const client = new PrismaClient() - await client.$connect() + const cookieSecret = parsed["COOKIE_SECRET"] ?? "VERY SECRET STRING" + const pool = new Pool({ + connectionString: parsed["DATABASE_URL"] + }) const deps = { appId, listenAddress, listenPort, clientSecret, cookieSecret, - channelManager: new ChannelManager(client), - userManager: new UserManager(client), + pool, botToken, publicKey, } diff --git a/src/commands/debug/UnjoinCommand.ts b/src/commands/debug/UnjoinCommand.ts new file mode 100644 index 0000000..5363b74 --- /dev/null +++ b/src/commands/debug/UnjoinCommand.ts @@ -0,0 +1,46 @@ +import {CommandContext, SlashCommand, SlashCreator} from "slash-create"; +import {Snowflake} from "discord-api-types"; +import {Pool} from "pg"; +import {singleQueryResult} from "../../queries/QueryHelpers.js"; +import {sendErrorMessage} from "../../queries/ErrorCodes.js"; + +export class UnjoinCommand extends SlashCommand { + readonly pool: Pool + + constructor(creator: SlashCreator, {pool, gameGuildIds}: { + pool: Pool, + gameGuildIds: Snowflake[], + }) { + super(creator, { + name: "debug_unjoin", + guildIDs: gameGuildIds, + description: "Allows an existing player to quit the game.", + options: [] + }); + + this.pool = pool + } + + async run(ctx: CommandContext): Promise { + try { + const result: number | undefined = singleQueryResult(await this.pool.query({ + text: "SELECT Command_Unjoin($1, $2, $3, $4, $5)", + values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator], + rowMode: "array", + })) + if (typeof result === "number") { + await ctx.send({ + content: `You got it! I've removed you from the register. Make sure to note down your old user ID: ${result}.`, + ephemeral: true, + }) + } else { + await ctx.send({ + content: `You're actually not on the register to begin with. So... mission accomplished?`, + ephemeral: true, + }) + } + } catch (e) { + await sendErrorMessage(ctx, e) + } + } +} \ No newline at end of file diff --git a/src/commands/game/JoinCommand.ts b/src/commands/game/JoinCommand.ts index 288c9e4..ee541e9 100644 --- a/src/commands/game/JoinCommand.ts +++ b/src/commands/game/JoinCommand.ts @@ -1,16 +1,14 @@ import {CommandContext, CommandOptionType, SlashCommand, SlashCreator} from "slash-create"; -import {ChannelManager} from "../../queries/ChannelManager.js"; import {Snowflake} from "discord-api-types"; -import {checkGameCommandAndRun} from "../permissions/ChannelPermissions.js"; -import {UserManager} from "../../queries/UserManager.js"; +import {singleRowQueryResult} from "../../queries/QueryHelpers.js"; +import {sendErrorMessage} from "../../queries/ErrorCodes.js"; +import {Pool} from "pg"; export class JoinCommand extends SlashCommand { - readonly channelManager: ChannelManager - readonly userManager: UserManager + readonly pool: Pool - constructor(creator: SlashCreator, {channelManager, userManager, gameGuildIds, genders}: { - channelManager: ChannelManager, - userManager: UserManager, + constructor(creator: SlashCreator, {pool, gameGuildIds, genders}: { + pool: Pool, gameGuildIds: Snowflake[], genders: { id: string, name: string }[], }) { @@ -38,30 +36,41 @@ export class JoinCommand extends SlashCommand { ] }); - this.channelManager = channelManager - this.userManager = userManager + this.pool = pool } - async run(ctx: CommandContext): Promise { - return checkGameCommandAndRun(ctx, this.channelManager, async () => { - const result = await this.userManager.registerOrReregisterUserFromDiscord({ - discordId: ctx.user.id, - username: ctx.user.username, - discriminator: ctx.user.discriminator, - name: ctx.options.name ?? "Anonymous", - genderId: ctx.options.gender ?? "x", - }) - if (result.created) { - return ctx.send({ - content: `You got it! Welcome aboard, ${result.user.name}! I have you down in my records as ${result.user.gender.name}. If you ever want to change your name or gender, just /join again!`, + async run(ctx: CommandContext): Promise { + const name = ctx.options.name ?? "Anonymous" + const gender = ctx.options.gender ?? "nb" + try { + const result = + singleRowQueryResult(await this.pool.query<{ resultid: number, newplayername: string, newgendername: string, wascreated: boolean }>({ + text: `SELECT resultId, newPlayerName, newGenderName, wasCreated + FROM Command_Join($1, $2, $3, $4, $5, $6, $7)`, + values: [ + ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator, + name, gender], + })) + console.log(result) + if (typeof result === "undefined") { + await ctx.send({ + content: "Unexpectedly got no results!!", + ephemeral: true, + }) + console.log("Unexpectedly empty Command_Join result!") + } else if (result.wascreated) { + await ctx.send({ + content: `You got it! Welcome aboard, ${result.newplayername}! I have you down in my records as ${result.newgendername}. If you ever want to change your name or gender, just /join again!`, ephemeral: true, }) } else { - return ctx.send({ - content: `Duly noted! I've updated your deets to have you down as ${result.user.name}, who is ${result.user.gender.name}. If you ever want to change your name or gender, just /join again!`, + await ctx.send({ + content: `Duly noted! I've updated your deets to have you down as ${result.newplayername}, who is ${result.newgendername}. If you ever want to change your name or gender, just /join again!`, ephemeral: true, }) } - }) + } catch (e) { + await sendErrorMessage(ctx, e) + } } } \ No newline at end of file diff --git a/src/commands/game/PullCommand.ts b/src/commands/game/PullCommand.ts index 109df3d..ae96c3c 100644 --- a/src/commands/game/PullCommand.ts +++ b/src/commands/game/PullCommand.ts @@ -1,16 +1,16 @@ import {CommandContext, CommandOptionType, SlashCommand, SlashCreator} from "slash-create"; import {Chance} from "chance"; -import {ChannelManager} from "../../queries/ChannelManager.js"; import {Snowflake} from "discord-api-types"; -import {checkGameCommandAndRun} from "../permissions/ChannelPermissions.js"; +import {Pool} from "pg"; +import {sendErrorMessage} from "../../queries/ErrorCodes.js"; const rand = Chance() export class PullCommand extends SlashCommand { - readonly channelManager: ChannelManager + readonly pool: Pool - constructor(creator: SlashCreator, {channelManager, gameGuildIds}: { - channelManager: ChannelManager, + constructor(creator: SlashCreator, {pool, gameGuildIds}: { + pool: Pool, gameGuildIds: Snowflake[] }) { super(creator, { @@ -29,12 +29,20 @@ export class PullCommand extends SlashCommand { ] }); - this.channelManager = channelManager + this.pool = pool } async run(ctx: CommandContext): Promise { - return checkGameCommandAndRun(ctx, this.channelManager, async () => { - const count: number = typeof ctx.options.count === "number" && ctx.options.count >= 1 && ctx.options.count <= 10 ? Math.floor(ctx.options.count) : 1 + try { + const count: number = + typeof ctx.options.count === "number" + && ctx.options.count >= 1 + && ctx.options.count <= 10 ? Math.floor(ctx.options.count) : 1 + await this.pool.query({ + text: `SELECT * + FROM Command_Pull($1, $2, $3, $4, $5, $6)`, + values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator, count], + }) const results: string[] = [] for (let x = 0; x < count; x += 1) { results.push(rand.weighted(["**Nicole**: D tier Predator Podcaster", @@ -47,6 +55,8 @@ export class PullCommand extends SlashCommand { content: `_${ctx.user.mention}_, you pulled...\n \\* ${results.join("\n \\* ")}`, ephemeral: false, }) - }) + } catch (e) { + await sendErrorMessage(ctx, e) + } } } \ No newline at end of file diff --git a/src/commands/permissions/ChannelPermissions.ts b/src/commands/permissions/ChannelPermissions.ts deleted file mode 100644 index c326bbd..0000000 --- a/src/commands/permissions/ChannelPermissions.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {CommandContext} from "slash-create"; -import {ChannelManager} from "../../queries/ChannelManager.js"; - -export async function checkGameCommandAndRun(ctx: CommandContext, channelManager: ChannelManager, handler: (ctx: CommandContext) => Promise): Promise { - const guildID = ctx.guildID - try { - if (await channelManager.canUseGameCommandsInChannel(ctx.channelID)) { - return handler(ctx) - } else if (guildID !== undefined && await channelManager.canUseGameCommandsInGuild(guildID)) { - return ctx.send({ - content: `Sorry, you can't do that in this channel.`, - ephemeral: true, - }) - } else { - return ctx.send({ - content: "Sorry, you can't do that in this guild.", - ephemeral: true, - }) - } - } catch (e) { - return ctx.send({ - content: `Uhhhhhh. Something went very wrong. If you see Reya, tell her I said ${e}.`, - ephemeral: true, - }) - } -} \ No newline at end of file diff --git a/src/queries/ChannelManager.ts b/src/queries/ChannelManager.ts deleted file mode 100644 index 6d46278..0000000 --- a/src/queries/ChannelManager.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {Snowflake} from "discord-api-types"; -import {PrismaClient} from "./Prisma.js"; - -export class ChannelManager { - readonly client: PrismaClient - - constructor(client: PrismaClient) { - this.client = client - } - - async getGameCommandGuildIds(): Promise { - return (await this.client.discordChannel.findMany({ - where: { - acceptGameCommands: true, - guildId: { - not: null - } - }, - distinct: ["guildId"], - select: { - guildId: true, - }, - })).map((item) => item.guildId as string) - // We know that the guild ID is not null because of the where condition. - } - - async canUseGameCommandsInChannel(channelId: Snowflake): Promise { - return ((await this.client.discordChannel.findUnique({ - where: { - discordId: channelId, - }, - select: { - acceptGameCommands: true, - }, - rejectOnNotFound: false, - })) ?? {acceptGameCommands: false}).acceptGameCommands - } - - async canUseGameCommandsInGuild(guildId: Snowflake): Promise { - return (await this.client.discordChannel.findFirst({ - where: { - guildId: guildId, - acceptGameCommands: true, - }, - select: { - discordId: true - } - })) !== null - } - - async getAdminCommandGuildIds(): Promise { - return (await this.client.discordChannel.findMany({ - where: { - acceptAdminCommands: true, - guildId: { - not: null - } - }, - distinct: ["guildId"], - select: { - guildId: true, - }, - })).map((item) => item.guildId as string) - // We know that the guild ID is not null because of the where condition. - } - - async canUseAdminCommandsInChannel(channelId: Snowflake): Promise { - return ((await this.client.discordChannel.findUnique({ - where: { - discordId: channelId, - }, - select: { - acceptAdminCommands: true, - }, - rejectOnNotFound: false, - })) ?? {acceptAdminCommands: false} - ).acceptAdminCommands - } - - async canUseAdminCommandsInGuild(guildId: Snowflake): Promise { - return (await this.client.discordChannel.findFirst({ - where: { - guildId: guildId, - acceptAdminCommands: true, - }, - select: { - discordId: true, - } - })) !== null - } - - async isGameReady() { - return false; - } -} \ No newline at end of file diff --git a/src/queries/ErrorCodes.ts b/src/queries/ErrorCodes.ts new file mode 100644 index 0000000..015402b --- /dev/null +++ b/src/queries/ErrorCodes.ts @@ -0,0 +1,48 @@ +import {CommandContext} from "slash-create"; +import {DatabaseError} from "pg"; + +export enum ErrorCodes { + BAD_CHANNEL_ADMIN = "VGBCA", + BAD_CHANNEL_GAME = "VGBCG", + BAD_GUILD_ADMIN = "VGBGA", + BAD_GUILD_GAME = "VGBGG", + NOT_YET_JOINED = "VGNYJ", + NOT_ENOUGH_CURRENCY = "VGNEC", +} + +/** Checks if the error is a database error. */ +export function isPostgresError(err: unknown): err is DatabaseError { + return err instanceof DatabaseError +} + +/** Sends a message detailing the given error on the given command context. */ +export async function sendErrorMessage(ctx: CommandContext, err: unknown): Promise { + console.log(err) + if (isPostgresError(err)) { + switch (err.code) { + case ErrorCodes.BAD_CHANNEL_ADMIN: + case ErrorCodes.BAD_CHANNEL_GAME: + case ErrorCodes.BAD_GUILD_ADMIN: + case ErrorCodes.BAD_GUILD_GAME: + case ErrorCodes.NOT_YET_JOINED: + case ErrorCodes.NOT_ENOUGH_CURRENCY: + await ctx.send({ + content: `**${err.message}**\n${err.detail}\n\n**Tip**: ${err.hint}`, + ephemeral: true, + }) + return + default: + await ctx.send({ + content: `**Unexpected Error (${err.code})**: ${err.message}\n${err.detail}\n\n**Tip**: ${err.hint}`, + ephemeral: true, + }) + return + } + } else { + await ctx.send({ + content: `**Unknown Error**: ${err}`, + ephemeral: true, + }) + return + } +} \ No newline at end of file diff --git a/src/queries/Prisma.ts b/src/queries/Prisma.ts deleted file mode 100644 index a30fa1b..0000000 --- a/src/queries/Prisma.ts +++ /dev/null @@ -1,9 +0,0 @@ -import pkg from "@prisma/client"; - -export const { - PrismaClient, - Prisma: PrismaNS, - prisma -} = pkg -export type PrismaClient = InstanceType; -export type {Prisma, DiscordUser, User, DiscordChannel} from "@prisma/client"; \ No newline at end of file diff --git a/src/queries/QueryHelpers.ts b/src/queries/QueryHelpers.ts new file mode 100644 index 0000000..6c1d37d --- /dev/null +++ b/src/queries/QueryHelpers.ts @@ -0,0 +1,13 @@ +import {QueryResult} from "pg"; + +export function singleQueryResult(result: QueryResult<[T]>): T | undefined { + return singleColumnQueryResult(result)[0] +} + +export function singleColumnQueryResult(result: QueryResult<[T]>): T[] { + return result.rows.map(([item]) => item) +} + +export function singleRowQueryResult(result: QueryResult): T | undefined { + return result.rows[0] +} \ No newline at end of file diff --git a/src/queries/UserManager.ts b/src/queries/UserManager.ts deleted file mode 100644 index 6651aba..0000000 --- a/src/queries/UserManager.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {DiscordUser, Prisma, PrismaClient} from "./Prisma.js"; -import {Snowflake} from "discord-api-types"; -import cuid from "cuid"; - -const userRegistrationSelect = { - id: true, - name: true, - discordUser: true, - gender: true, - joinedAt: true, -} as const -export type UserRegistrationData = Prisma.UserGetPayload<{ select: typeof userRegistrationSelect }> - -const genderListSelect = { - id: true, - name: true, -} as const -export type GenderListData = Prisma.GenderGetPayload<{ select: typeof genderListSelect }> - -export class UserManager { - readonly client: PrismaClient - - constructor(client: PrismaClient) { - this.client = client - } - - async registerOrUpdateDiscordUser({ - discordId, - username, - discriminator, - }: { discordId: Snowflake, username: string, discriminator: string }): Promise { - return (await this.client.discordUser.upsert({ - where: { - discordId, - }, - create: { - discordId, - username, - discriminator, - userId: null, - }, - update: { - username, - discriminator, - user: { - update: { - lastActive: new Date() - } - } - }, - include: { - user: true, - } - })) - } - - async registerOrReregisterUserFromDiscord({ - discordId, - username, - discriminator, - name, - genderId - }: { discordId: Snowflake, username: string, discriminator: string, name: string, genderId: string }): Promise<{ - user: UserRegistrationData, created: boolean - }> { - const userId = cuid() - const user = (await this.client.discordUser.upsert({ - where: { - discordId, - }, - update: { - username, - discriminator, - user: { - upsert: { - update: { - name, - gender: { - connect: { - id: genderId, - } - }, - lastActive: new Date() - }, - create: { - id: userId, - name, - gender: { - connect: { - id: genderId, - } - }, - } - } - } - }, - create: { - discordId, - username, - discriminator, - user: { - create: { - id: userId, - name, - gender: { - connect: { - id: genderId, - } - }, - } - } - }, - select: { - user: { - select: userRegistrationSelect - } - }, - })).user - if (user === null) { - throw Error("...Somehow, there wasn't a user to return?!") - } - return { - user, - created: user.id === userId, - } - } - - async getGenders(): Promise { - return this.client.gender.findMany({ - select: genderListSelect - }) - } -} \ No newline at end of file diff --git a/src/tools/LoadGenders.ts b/src/tools/LoadGenders.ts deleted file mode 100644 index abce500..0000000 --- a/src/tools/LoadGenders.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {PrismaClient} from "../queries/Prisma.js"; - -async function main() { - const client = new PrismaClient() - await client.$connect() - await client.gender.upsert({ - where: { - id: "f" - }, - create: { - id: "f", - name: "Female" - }, - update: { - name: "Female" - }, - }) - await client.gender.upsert({ - where: { - id: "m" - }, - create: { - id: "m", - name: "Male" - }, - update: { - name: "Male" - }, - }) - await client.gender.upsert({ - where: { - id: "x" - }, - create: { - id: "x", - name: "Non-binary" - }, - update: { - name: "Non-binary" - }, - }) -} - -main() \ No newline at end of file diff --git a/src/tools/LoadJsonWebhooks.ts b/src/tools/LoadJsonWebhooks.ts index a90d612..dfc8c30 100644 --- a/src/tools/LoadJsonWebhooks.ts +++ b/src/tools/LoadJsonWebhooks.ts @@ -1,54 +1,30 @@ -import {Prisma, PrismaClient} from "../queries/Prisma.js"; import {APIWebhook} from "discord-api-types"; import {readFile} from "fs/promises"; +import {Pool} from "pg"; +import dotenv from "dotenv"; -type DiscordChannelPermissions = Prisma.DiscordChannelGetPayload<{ - select: { - broadcastGame: true, - sendLogs: true, - acceptGameCommands: true, - acceptAdminCommands: true, - } -}> - -async function loadHookIntoDatabase(client: PrismaClient, hook: APIWebhook, permissions: DiscordChannelPermissions): Promise { - await client.discordChannel.upsert({ - where: {discordId: hook.channel_id}, - update: { - webhookId: hook.id, - token: hook.token, - ...permissions - }, - create: { - discordId: hook.channel_id, - guildId: hook.guild_id ?? null, - webhookId: hook.id, - token: hook.token, - name: "???", - priority: 0, - ...permissions - } +async function loadHooksIntoDatabase(client: Pool, gameHook: APIWebhook & { channelName: string }, adminHook: APIWebhook & { channelName: string }): Promise { + await client.query({ + text: ` + INSERT INTO DiscordChannel (discordId, name, broadcastGame, sendLogs, acceptGameCommands, + acceptAdminCommands, priority, guildId, webhookId, webhookToken) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10), + ($11, $12, $13, $14, $15, $16, $17, $18, $19, $20) + ON CONFLICT DO NOTHING`, + values: [gameHook.channel_id, gameHook.channelName, true, false, true, false, 0, gameHook.guild_id, gameHook.id, gameHook.token, + adminHook.channel_id, adminHook.channelName, false, true, false, true, 0, adminHook.guild_id, adminHook.id, adminHook.token] }) } async function main() { - const client = new PrismaClient() - await client.$connect() - - const gameHook: APIWebhook = JSON.parse(await readFile("runtime/webhooks/game.json", {encoding: "utf-8"})) - await loadHookIntoDatabase(client, gameHook, { - acceptAdminCommands: false, - acceptGameCommands: true, - sendLogs: false, - broadcastGame: true, - }) - const adminHook: APIWebhook = JSON.parse(await readFile("runtime/webhooks/admin.json", {encoding: "utf-8"})) - await loadHookIntoDatabase(client, adminHook, { - acceptAdminCommands: true, - acceptGameCommands: false, - sendLogs: true, - broadcastGame: false, + const {DATABASE_URL: connectionString} = dotenv.config().parsed ?? {} + const client = new Pool({ + connectionString }) + + const gameHook: APIWebhook & { channelName: string } = JSON.parse(await readFile("runtime/webhooks/game.json", {encoding: "utf-8"})) + const adminHook: APIWebhook & { channelName: string } = JSON.parse(await readFile("runtime/webhooks/admin.json", {encoding: "utf-8"})) + await loadHooksIntoDatabase(client, gameHook, adminHook) } main() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3a50d26..731cbd0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "strictNullChecks": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, - "module": "ESNext", + "module": "CommonJS", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true,