Beginnings of character creation commands.

main
Mari 2 years ago
parent 88018a0f16
commit 1fd98e04c6
  1. 194
      migrations/0001-characters.sql
  2. 7
      src/bin/bot.ts
  3. 18
      src/database/battle_types.ts
  4. 64
      src/database/character_creation.ts
  5. 65
      src/database/database.ts
  6. 22
      src/database/pronouns.ts
  7. 15
      src/database/users.ts

@ -13,82 +13,120 @@ CREATE TABLE IF NOT EXISTS users
); );
--rollback DROP TABLE IF EXISTS users; --rollback DROP TABLE IF EXISTS users;
--changeset reya:types_table runInTransaction:false --changeset reya:battle_types_table runInTransaction:false
CREATE TABLE IF NOT EXISTS types CREATE TABLE IF NOT EXISTS battle_types
( (
id INT NOT NULL PRIMARY KEY, id INT NOT NULL PRIMARY KEY,
name STRING NOT NULL, name STRING NOT NULL UNIQUE,
emoji STRING NOT NULL UNIQUE,
color STRING NOT NULL, color STRING NOT NULL,
display_order INT NOT NULL, display_order INT NOT NULL,
immunities INT[] NOT NULL DEFAULT '{}', immunities INT[] NOT NULL DEFAULT '{}',
resistances INT[] NOT NULL DEFAULT '{}', resistances INT[] NOT NULL DEFAULT '{}',
weaknesses INT[] NOT NULL DEFAULT '{}' weaknesses INT[] NOT NULL DEFAULT '{}'
); );
--rollback DROP TABLE IF EXISTS types; --rollback DROP TABLE IF EXISTS battle_types;
--changeset reya:types_values runInTransaction:true --changeset reya:battle_types_values runInTransaction:true
INSERT INTO types (id, name, color, display_order) INSERT INTO battle_types (id, name, emoji, color, display_order)
VALUES (0, 'Basic', '', 0), VALUES (0, 'Basic', '🔲', '', 0),
(1, 'Sassy', '', 1), (1, 'Sassy', '🔥', '', 1),
(2, 'Gentle', '', 2), (2, 'Gentle', '💙', '', 2),
(3, 'Sexy', '', 3), (3, 'Sexy', '💋', '', 3),
(4, 'Muscle', '', 4), (4, 'Muscle', '💪', '', 4),
(5, 'Glam', '', 5), (5, 'Glam', '', '', 5),
(6, 'Punk', '', 6), (6, 'Punk', '🎸', '', 6),
(7, 'Glutton', '', 7), (7, 'Glutton', '🍗', '', 7),
(8, 'Dumb', '', 8), (8, 'Dumb', '🪨', '', 8),
(9, 'Drone', '', 9), (9, 'Drone', '🤖', '', 9),
(10, 'Spooky', '', 10), (10, 'Spooky', '👻', '', 10),
(11, 'Lively', '', 11), (11, 'Lively', '🎉', '', 11),
(12, 'Smart', '', 12), (12, 'Smart', '🧠', '', 12),
(13, 'Cool', '', 13), (13, 'Cool', '🧊', '', 13),
(14, 'Bully', '', 14), (14, 'Bully', '😈', '', 14),
(15, 'Mythic', '', 15), (15, 'Mythic', '🔱', '', 15),
(16, 'Toy', '', 16), (16, 'Toy', '🧸', '', 16),
(17, 'Cute', '', 17) (17, 'Cute', '🌺', '', 17)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
--rollback DELETE FROM types WHERE id in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17) --rollback DELETE FROM battle_types WHERE id in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
--changeset reya:difficulties_table runInTransaction:false --changeset reya:difficulties_table runInTransaction:false
CREATE TABLE IF NOT EXISTS difficulties CREATE TABLE IF NOT EXISTS difficulties
( (
id INT NOT NULL PRIMARY KEY, id INT NOT NULL PRIMARY KEY,
name STRING NOT NULL UNIQUE, name STRING NOT NULL UNIQUE,
description STRING NOT NULL, emoji STRING NOT NULL UNIQUE,
short_description STRING NOT NULL,
long_description STRING NOT NULL,
display_order INT NOT NULL, display_order INT NOT NULL,
endo_only BOOLEAN NOT NULL DEFAULT false, allow_digestion BOOLEAN NOT NULL DEFAULT true,
regenerate_talent BOOLEAN NOT NULL DEFAULT false, regenerate_talent BOOLEAN NOT NULL DEFAULT false,
reformation_health_percent FLOAT NOT NULL DEFAULT 100,
reformation_stamina_percent FLOAT NOT NULL DEFAULT 100,
lose_proficiency_percent FLOAT NOT NULL DEFAULT 0, lose_proficiency_percent FLOAT NOT NULL DEFAULT 0,
lose_exp_percent FLOAT NOT NULL DEFAULT 0, lose_exp_percent FLOAT NOT NULL DEFAULT 0,
lose_money_percent FLOAT NOT NULL DEFAULT 0, lose_money_percent FLOAT NOT NULL DEFAULT 0
allow_reformation BOOLEAN NOT NULL DEFAULT true
); );
--rollback DROP TABLE IF EXISTS difficulties; --rollback DROP TABLE IF EXISTS difficulties;
--changeset reya:difficulties_values runInTransaction:true --changeset reya:difficulties_values runInTransaction:true
INSERT INTO difficulties INSERT INTO difficulties
(id, name, description, display_order, endo_only, regenerate_talent, lose_proficiency_percent, lose_exp_percent, (id, name, emoji, short_description, long_description, display_order, allow_digestion, regenerate_talent,
lose_money_percent, allow_reformation) reformation_health_percent, reformation_stamina_percent, lose_proficiency_percent, lose_exp_percent,
VALUES (0, 'Endo Only', 'You can never be digested. You always come out sooner or later.', 0, true, false, 0, 0, 0, lose_money_percent)
true), VALUES (0, 'Indigestible', '🔰',
(1, 'Very Safe', 'You can''t be digested. Stomachs just make you sleepy, and you always come out none the worse for wear.',
'You''ll be completely safe. The only downsides to being digested? Inconvenience. And smug predators.', 1, 'Stomachs have no effect on you. When your health is reduced to 0, you simply become tired and drift off to '
false, false, 0, 0, 0, true), ||
(2, 'Safe', 'sleep. You don''t need to be reformed after battles when this happens, and you won''t lose anything for '
'Your noggin will get rattled around, sure, and you might drop some of your cash, but it''s a small price to pay for being able to come back.', || 'falling asleep this way. You''ll be ready to go right away as soon as you''re released!'
2, false, true, 10, 0, 10, true), || e'\n\n'
(3, 'Risky', || 'Suitable for players who play with the Pred Only or Spectator preferences, or who don''t want their '
'Getting digested is going to mess with your head, for sure. You''ll come back a bit weakened from the experience, and your wallet will notice. This is the recommended difficulty.', || 'character to die even temporarily.',
3, false, true, 25, 10, 25, true), 0, false, false, 100, 100, 0, 0, 0),
(4, 'Dangerous', (1, 'Very Safe', '',
'You''ll be completely safe. The only downsides to being digested? Inconvenience. And smug predators.',
'Reformation is a cakewalk for you. You can be digested, but you can reform after battle with no penalties '
|| 'of any kind. Your body and mind are completely unaffected by the visit to someone else''s gut.'
|| e'\n\n'
|| 'Reformation leaves you a little bit weary, but in good health.'
|| e'\n\n'
|| 'Suitable for players who prefer a digestive end, but don''t want to lose any progress.',
1, true, false, 100, 90, 0, 0, 0),
(2, 'Safe', '🌤',
'There''s a small price to being digested, but you didn''t think cheating death would be free, right?',
'Digestion takes a little bit of a toll on your body and mind. And while you do come back, you don''t come '
|| 'back _quite_ the way you left.'
|| 'Your proficiencies will drop by 10% as a little of what you''ve taken from your past exploits fades '
|| 'from you. You''ll also lose 10% of your money as a cost for the reformation process.'
|| e'\n\n'
|| 'Reformation leaves you a bit weakened and fairly drowsy. You should rest a bit before continuing.'
|| e'\n\n'
|| 'Suitable for players who want to add a little risk to being digested, but don''t want to lose levels.',
2, true, false, 90, 75, 10, 0, 10),
(3, 'Risky', '🌥',
'Reformation is a painful experience. It''s no laughing matter to be digested, so watch your back.',
'Digestion is no walk in the park for you. When you come back, you leave some of yourself in the predator who '
|| 'turned you from a person to a snack. Your proficiencies drop by 25% as some of what you''ve taken from '
|| 'your past conquests fades from you, and your talents will be re-randomized, as the process of '
|| 'reformation is more art than science and your body and mind will not be quite the same. You''ll also '
|| 'lose 25% of your money as the cost of returning from being belly paunch. Worst of all, you''ll lose '
|| '10% of your experience points as your memories are clouded by your brush with fatality.'
|| e'\n\n'
|| ''
|| e'\n\n'
|| 'Suitable for players who want a balance between risk and maintaining their progress.',
3, true, true, 25, 10, 25),
(4, 'Dangerous', '🌦',
'Digestion pushes your',
'Digestion is something to be avoided at all costs. You''ll lose half your money, your stats will have atrophied, and you''ll lose some of your memories.', 'Digestion is something to be avoided at all costs. You''ll lose half your money, your stats will have atrophied, and you''ll lose some of your memories.',
4, false, true, 50, 25, 50, true), 4, true, true, 50, 25, 50),
(5, 'Very Dangerous', (5, 'Very Dangerous', '🌧', '',
'Being a meal is not just humiliating - it''s a nightmare. You''re lucky you get to hold on to anything.', 5, 'Being a meal is not just humiliating - it''s a nightmare. You''re lucky you get to hold on to anything.',
false, true, 90, 50, 90, true), 5, true, true, 90, 50, 90),
(6, 'Extremely Dangerous', (6, 'Extremely Dangerous', '', '',
'If you get devoured and you don''t get out, you''ll lose just about everything. Be very, very careful...', 6, 'If you get devoured and you don''t get out, you''ll lose just about everything. Be very, very careful...',
false, true, 100, 100, 100, true) 6, true, true, 100, 100, 100)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
--rollback DELETE FROM difficulties WHERE id IN (0, 1, 2, 3, 4, 5, 6, 7); --rollback DELETE FROM difficulties WHERE id IN (0, 1, 2, 3, 4, 5, 6, 7);
@ -97,7 +135,9 @@ CREATE TABLE IF NOT EXISTS preferences
( (
id INT NOT NULL PRIMARY KEY, id INT NOT NULL PRIMARY KEY,
name STRING NOT NULL UNIQUE, name STRING NOT NULL UNIQUE,
description STRING NOT NULL, emoji STRING NOT NULL UNIQUE,
short_description STRING NOT NULL,
long_description STRING NOT NULL,
display_order INT NOT NULL, display_order INT NOT NULL,
can_use_vore BOOLEAN NOT NULL, can_use_vore BOOLEAN NOT NULL,
can_receive_vore BOOLEAN NOT NULL can_receive_vore BOOLEAN NOT NULL
@ -105,19 +145,20 @@ CREATE TABLE IF NOT EXISTS preferences
--rollback DROP TABLE IF EXISTS preferences; --rollback DROP TABLE IF EXISTS preferences;
--changeset reya:preferences_values runInTransaction:true --changeset reya:preferences_values runInTransaction:true
INSERT INTO preferences (id, name, description, display_order, can_use_vore, can_receive_vore) INSERT INTO preferences (id, name, emoji, short_description, long_description, display_order, can_use_vore,
VALUES (0, 'Observer', 'You can neither eat nor be eaten.', 0, false, false), can_receive_vore)
(1, 'Prey Only', 'You can only be eaten, not eat.', 1, false, true), VALUES (0, 'Observer', '', 'You can neither eat nor be eaten.', '', 0, false, false),
(2, 'Pred Only', 'You can only eat, not be eaten.', 2, true, false), (1, 'Prey Only', '', 'You can only be eaten, not eat.', '', 1, false, true),
(3, 'Switch', 'You can both eat and be eaten.', 3, true, true) (2, 'Pred Only', '', 'You can only eat, not be eaten.', '', 2, true, false),
(3, 'Switch', '', 'You can both eat and be eaten.', '', 3, true, true)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
--rollback DELETE FROM preferences WHERE id IN (0, 1, 2, 3); --rollback DELETE FROM preferences WHERE id IN (0, 1, 2, 3);
--changeset reya:genders_table runInTransaction:false --changeset reya:pronouns_table runInTransaction:false
CREATE TABLE IF NOT EXISTS genders CREATE TABLE IF NOT EXISTS pronouns
( (
id INT NOT NULL PRIMARY KEY, id INT NOT NULL PRIMARY KEY,
name STRING NOT NULL, default_gender STRING NOT NULL,
pronouns STRING NOT NULL UNIQUE, pronouns STRING NOT NULL UNIQUE,
display_order INT NOT NULL, display_order INT NOT NULL,
use_plural BOOLEAN NOT NULL, use_plural BOOLEAN NOT NULL,
@ -127,16 +168,16 @@ CREATE TABLE IF NOT EXISTS genders
reflexive STRING NOT NULL, reflexive STRING NOT NULL,
objective STRING NOT NULL objective STRING NOT NULL
); );
--rollback DROP TABLE IF EXISTS genders; --rollback DROP TABLE IF EXISTS pronouns;
--changeset reya:genders_values runInTransaction:true --changeset reya:pronouns_values runInTransaction:true
INSERT INTO genders (id, name, pronouns, display_order, use_plural, subjective, adjective, possessive, reflexive, INSERT INTO pronouns (id, default_gender, pronouns, display_order, use_plural, subjective, adjective, possessive,
objective) reflexive, objective)
VALUES (0, 'Non-binary', 'name only', 0, false, '@@', '@@''s', '@@''s', '@@''s self', '@@'), VALUES (0, 'Genderless', 'none', 0, false, '@@', '@@''s', '@@''s', '@@''s self', '@@'),
(1, 'Female', 'she/her', 1, false, 'she', 'her', 'hers', 'herself', 'her'), (1, 'Female', 'she/her', 1, false, 'she', 'her', 'hers', 'herself', 'her'),
(2, 'Non-binary', 'they/them', 2, true, 'they', 'their', 'theirs', 'themself', 'them'), (2, 'Non-binary', 'they/them', 2, true, 'they', 'their', 'theirs', 'themself', 'them'),
(3, 'Male', 'he/him', 3, false, 'he', 'his', 'his', 'himself', 'him'), (3, 'Male', 'he/him', 3, false, 'he', 'his', 'his', 'himself', 'him'),
(4, 'Object', 'it/its', 4, false, 'it', 'its', 'its', 'itself', 'it'), (4, 'Genderless', 'it/its', 4, false, 'it', 'its', 'its', 'itself', 'it'),
(5, 'Herm', 'shi/hir', 5, false, 'shi', 'hir', 'hirs', 'hirself', 'hir'), (5, 'Herm', 'shi/hir', 5, false, 'shi', 'hir', 'hirs', 'hirself', 'hir'),
(6, 'Non-binary', 'ae/aer', 6, false, 'ae', 'aer', 'aers', 'aerself', 'aer'), (6, 'Non-binary', 'ae/aer', 6, false, 'ae', 'aer', 'aers', 'aerself', 'aer'),
(7, 'Non-binary', 'fae/faer', 7, false, 'fae', 'faer', 'faers', 'faerself', 'faer'), (7, 'Non-binary', 'fae/faer', 7, false, 'fae', 'faer', 'faers', 'faerself', 'faer'),
@ -151,7 +192,7 @@ VALUES (0, 'Non-binary', 'name only', 0, false, '@@', '@@''s', '@@''s', '@@''s s
(16, 'Non-binary', 'sie/sie', 16, false, 'sie', 'hir', 'hirs', 'hirself', 'sie'), (16, 'Non-binary', 'sie/sie', 16, false, 'sie', 'hir', 'hirs', 'hirself', 'sie'),
(17, 'Non-binary', 'te/ter', 17, false, 'te', 'tem', 'ters', 'terself', 'ter') (17, 'Non-binary', 'te/ter', 17, false, 'te', 'tem', 'ters', 'terself', 'ter')
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
--rollback DELETE FROM genders WHERE id IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); --rollback DELETE FROM pronouns WHERE id IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
--changeset reya:characters_table runInTransaction:false --changeset reya:characters_table runInTransaction:false
CREATE TABLE IF NOT EXISTS characters CREATE TABLE IF NOT EXISTS characters
@ -162,11 +203,12 @@ CREATE TABLE IF NOT EXISTS characters
discriminator INT NULL DEFAULT NULL, discriminator INT NULL DEFAULT NULL,
title STRING NOT NULL, title STRING NOT NULL,
profile STRING NOT NULL, profile STRING NOT NULL,
gender_id INT NOT NULL REFERENCES genders (id) ON DELETE RESTRICT, pronouns_id INT NOT NULL REFERENCES pronouns (id) ON DELETE RESTRICT,
gender_name STRING NOT NULL,
difficulty_id INT NOT NULL REFERENCES difficulties (id) ON DELETE RESTRICT, difficulty_id INT NOT NULL REFERENCES difficulties (id) ON DELETE RESTRICT,
preference_id INT NOT NULL REFERENCES preferences (id) ON DELETE RESTRICT, preference_id INT NOT NULL REFERENCES preferences (id) ON DELETE RESTRICT,
type1_id INT NOT NULL REFERENCES types (id) ON DELETE RESTRICT, battle_type1_id INT NOT NULL REFERENCES battle_types (id) ON DELETE RESTRICT,
type2_id INT NOT NULL REFERENCES types (id) ON DELETE RESTRICT, battle_type2_id INT NOT NULL REFERENCES battle_types (id) ON DELETE RESTRICT,
experience INT NOT NULL DEFAULT 0, experience INT NOT NULL DEFAULT 0,
money INT NOT NULL DEFAULT 0, money INT NOT NULL DEFAULT 0,
@ -208,7 +250,8 @@ CREATE TABLE IF NOT EXISTS characters
speed_proficiency INT NOT NULL DEFAULT 0, speed_proficiency INT NOT NULL DEFAULT 0,
UNIQUE (user_id, name, discriminator), UNIQUE (user_id, name, discriminator),
FAMILY character_base (id, user_id, name, title, profile, gender_id, type1_id, type2_id, base_confidence, FAMILY character_base (id, user_id, name, title, profile, pronouns_id, battle_type1_id, battle_type2_id,
base_confidence,
base_health, base_health,
base_stamina, base_brawn, base_durability, base_intensity, base_resilience, base_speed), base_stamina, base_brawn, base_durability, base_intensity, base_resilience, base_speed),
FAMILY character_reformation_stats (min_confidence_talent, min_health_talent, min_stamina_talent, min_brawn_talent, FAMILY character_reformation_stats (min_confidence_talent, min_health_talent, min_stamina_talent, min_brawn_talent,
@ -230,11 +273,12 @@ CREATE TABLE IF NOT EXISTS character_creation
name STRING NULL, name STRING NULL,
title STRING NULL, title STRING NULL,
profile STRING NULL, profile STRING NULL,
gender_id INT NULL REFERENCES genders (id) ON DELETE RESTRICT, pronouns_id INT NULL REFERENCES pronouns (id) ON DELETE RESTRICT,
gender_name STRING NULL,
difficulty_id INT NULL REFERENCES difficulties (id) ON DELETE RESTRICT, difficulty_id INT NULL REFERENCES difficulties (id) ON DELETE RESTRICT,
preference_id INT NULL REFERENCES preferences (id) ON DELETE RESTRICT, preference_id INT NULL REFERENCES preferences (id) ON DELETE RESTRICT,
type1_id INT NULL REFERENCES types (id) ON DELETE RESTRICT, battle_type1_id INT NULL REFERENCES battle_types (id) ON DELETE RESTRICT,
type2_id INT NULL REFERENCES types (id) ON DELETE RESTRICT, battle_type2_id INT NULL REFERENCES battle_types (id) ON DELETE RESTRICT,
base_confidence INT NULL DEFAULT 70, base_confidence INT NULL DEFAULT 70,
base_health INT NULL DEFAULT 70, base_health INT NULL DEFAULT 70,
base_stamina INT NULL DEFAULT 70, base_stamina INT NULL DEFAULT 70,
@ -250,8 +294,10 @@ CREATE TABLE IF NOT EXISTS character_creation
--changeset reya:userDefaultDifficultyPreferenceGender --changeset reya:userDefaultDifficultyPreferenceGender
ALTER TABLE users ALTER TABLE users
ADD COLUMN default_gender_id INT NULL REFERENCES genders (id) ON DELETE RESTRICT DEFAULT NULL ADD COLUMN default_pronouns_id INT NULL REFERENCES pronouns (id) ON DELETE RESTRICT DEFAULT NULL
CREATE IF NOT EXISTS FAMILY character_defaults, CREATE IF NOT EXISTS FAMILY character_defaults,
ADD COLUMN default_gender_name STRING NULL DEFAULT NULL
FAMILY character_defaults,
ADD COLUMN default_difficulty_id INT NULL REFERENCES difficulties (id) ON DELETE RESTRICT DEFAULT NULL ADD COLUMN default_difficulty_id INT NULL REFERENCES difficulties (id) ON DELETE RESTRICT DEFAULT NULL
FAMILY character_defaults, FAMILY character_defaults,
ADD COLUMN default_preference_id INT NULL REFERENCES preferences (id) ON DELETE RESTRICT DEFAULT NULL ADD COLUMN default_preference_id INT NULL REFERENCES preferences (id) ON DELETE RESTRICT DEFAULT NULL

@ -5,7 +5,7 @@ import {Commands} from "../commands/index.js"
import {checkIsRestart, reportFailed, reportReady, reportStarted} from "../ipc/restart.js" import {checkIsRestart, reportFailed, reportReady, reportStarted} from "../ipc/restart.js"
import {defaultPresence} from "../defaultPresence.js" import {defaultPresence} from "../defaultPresence.js"
import {Pool} from "pg" import {Pool} from "pg"
import {Database, DatabaseImpl} from "../database/database.js" import {Database, DatabaseImpl, makeTransactable} from "../database/database.js"
async function bot() { async function bot() {
await checkIsRestart() await checkIsRestart()
@ -22,7 +22,10 @@ async function bot() {
c.destroy() c.destroy()
} }
const db: Database = new DatabaseImpl(p.query.bind(p)) const db: Database = new DatabaseImpl({
query: p.query.bind(p),
transactable: makeTransactable(p),
})
const cmd = new Commands({users: db.users, cleanUp}) const cmd = new Commands({users: db.users, cleanUp})
c.on("ready", async () => { c.on("ready", async () => {

@ -0,0 +1,18 @@
export interface BattleType {
id: number,
name: string,
emoji: string,
color: string,
displayOrder: number,
immmunities: BattleTypeStub[]
resistances: BattleTypeStub[]
weaknesses: BattleTypeStub[]
}
export interface BattleTypeStub {
id: number,
name: string,
emoji: string,
color: string,
displayOrder: number
}

@ -0,0 +1,64 @@
import {QueryType, Transactable} from "./database.js"
import {Snowflake} from "discord-api-types/globals.js"
import {Pronouns} from "./pronouns.js"
export interface CharacterCreationState {
name: string | null
title: string | null
profile: string | null
gender: string | null
pronouns: Pronouns | null
difficulty: Difficulty | null
preference: Preference | null
type1: BattleType | null,
type2: BattleType | null,
baseConfidence: number | null,
baseHealth: number | null,
baseStamina: number | null,
baseBrawn: number | null,
baseDurability: number | null,
baseIntensity: number | null,
baseResilience: number | null,
baseSpeed: number | null,
}
export interface CharacterCreationInput {
name?: string
title?: string
profile?: string
gender?: string
pronouns?: string
difficultyName?: string
preferenceName?: string
type1Name?: string
type2Name?: string
baseConfidence?: number
baseHealth?: number
baseStamina?: number
baseBrawn?: number
baseDurability?: number
baseIntensity?: number
baseResilience?: number
baseSpeed?: number
}
export interface CharacterCreationTable {
startOrContinueCreatingCharacter(snowflake: Snowflake,
input: CharacterCreationInput): Promise<CharacterCreationState>
}
export class CharacterCreationTableImpl implements CharacterCreationTable {
private readonly _query: QueryType
private readonly _transactable: Transactable
constructor({query, transactable}: { query: QueryType, transactable: Transactable }) {
this._query = query
this._transactable = transactable
}
async startOrContinueCreatingCharacter(
owner: Snowflake,
input: CharacterCreationInput): Promise<CharacterCreationState> {
}
}

@ -1,16 +1,71 @@
import {Client} from "pg" import {QueryResult, QueryResultRow} from "pg"
import {UsersTable, UsersTableImpl} from "./users.js" import {UsersTable, UsersTableImpl} from "./users.js"
import {CharacterCreationTable, CharacterCreationTableImpl} from "./character_creation.js"
export interface PoolLike {
connect(): Promise<PoolClientLike>
}
export interface PoolClientLike extends Queryable {
release(err?: boolean): void
}
export function makeTransactable(p: PoolLike): Transactable {
return async function transactable(callback: (q: QueryType, attempts: number) => Promise<void>): Promise<void> {
const client = await p.connect()
const query = client.query.bind(client)
let committed = false
let attempts = 0
while (!committed) {
try {
await client.query("BEGIN")
} catch (err) {
client.release()
throw err
}
try {
await callback(query, attempts)
} catch (err) {
try {
await client.query("ROLLBACK")
client.release()
} catch (err) {
client.release(true)
}
throw err
}
try {
await client.query("COMMIT")
committed = true
} catch (err) {
attempts += 1
}
}
client.release()
}
}
export interface Database { export interface Database {
readonly users: UsersTable readonly users: UsersTable
readonly characterCreation: CharacterCreationTable
} }
export interface Queryable {
query<RowT extends QueryResultRow>(queryText: string): Promise<QueryResult<RowT>>
query<RowT extends QueryResultRow, ParamT extends any[]>(queryText: string,
params: ParamT): Promise<QueryResult<RowT>>
}
export type QueryType = Queryable["query"]
export type Transactable = (transaction: (client: QueryType, attempts: number) => Promise<void>) => Promise<void>
export class DatabaseImpl implements Database { export class DatabaseImpl implements Database {
readonly users: UsersTableImpl readonly users: UsersTableImpl
private readonly _query: Client["query"] readonly characterCreation: CharacterCreationTableImpl
constructor(query: Client["query"]) { constructor({query, transactable}: { query: QueryType, transactable: Transactable }) {
this._query = query this.users = new UsersTableImpl({query})
this.users = new UsersTableImpl(this._query) this.characterCreation = new CharacterCreationTableImpl({query, transactable})
} }
} }

@ -0,0 +1,22 @@
export interface Pronouns {
id: number
defaultGender: string
pronouns: string
displayOrder: number
usePlural: boolean
subjective: string
adjective: string
possessive: string
reflexive: string
objective: string
}
export interface PronounsAutocomplete {
pronouns: string
usePlural: boolean
subjective: string
adjective: string
possessive: string
reflexive: string
objective: string
}

@ -2,7 +2,7 @@ import {Snowflake} from "discord-api-types/globals.js"
import {fast1a52 as fnvFast1a52} from "fnv-plus" import {fast1a52 as fnvFast1a52} from "fnv-plus"
import {parse as uuidParse, v1 as uuidV1, validate as uuidValidate, version as uuidVersion} from "uuid" import {parse as uuidParse, v1 as uuidV1, validate as uuidValidate, version as uuidVersion} from "uuid"
import {SnowflakeUtil} from "discord.js" import {SnowflakeUtil} from "discord.js"
import {Client} from "pg" import {QueryType} from "./database.js"
export interface UsersTable { export interface UsersTable {
getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string> getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string>
@ -13,14 +13,15 @@ export interface UsersTable {
} }
export class UsersTableImpl implements UsersTable { export class UsersTableImpl implements UsersTable {
private readonly _query: Client["query"] private readonly _query: QueryType
constructor(query: Client["query"]) { constructor({query}: { query: QueryType }) {
this._query = query this._query = query
} }
async getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string> { async getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string> {
const result = await this._query(`INSERT INTO users (id, is_admin, created_at, updated_at, active_at) const result = await this._query<{ id: string }, [string]>(
`INSERT INTO users (id, is_admin, created_at, updated_at, active_at)
VALUES ($1, FALSE, now(), now(), now()) VALUES ($1, FALSE, now(), now(), now())
ON CONFLICT (id) DO UPDATE SET active_at = now() ON CONFLICT (id) DO UPDATE SET active_at = now()
RETURNING id;`, [userSnowflakeToUuid(snowflake)]) RETURNING id;`, [userSnowflakeToUuid(snowflake)])
@ -28,7 +29,8 @@ export class UsersTableImpl implements UsersTable {
} }
async getActiveSnowflakeIsAdmin(snowflake: Snowflake): Promise<boolean> { async getActiveSnowflakeIsAdmin(snowflake: Snowflake): Promise<boolean> {
const result = await this._query(`UPDATE users const result = await this._query<{ is_admin: boolean }, [string]>(
`UPDATE users
SET active_at = NOW() SET active_at = NOW()
WHERE id = $1 WHERE id = $1
RETURNING is_admin;`, [userSnowflakeToUuid(snowflake)]) RETURNING is_admin;`, [userSnowflakeToUuid(snowflake)])
@ -39,7 +41,8 @@ export class UsersTableImpl implements UsersTable {
} }
async createBotOwnerAsAdmin(snowflake: Snowflake): Promise<void> { async createBotOwnerAsAdmin(snowflake: Snowflake): Promise<void> {
await this._query(`INSERT INTO users (id, is_admin, created_at, updated_at) await this._query<{}, [string]>(
`INSERT INTO users (id, is_admin, created_at, updated_at)
VALUES ($1, TRUE, now(), now()) VALUES ($1, TRUE, now(), now())
ON CONFLICT (id) DO UPDATE SET is_admin = TRUE, ON CONFLICT (id) DO UPDATE SET is_admin = TRUE,
updated_at = NOW() updated_at = NOW()

Loading…
Cancel
Save