You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
vore-rpg/src/database/users.ts

113 lines
4.6 KiB

import {Snowflake} from "discord-api-types/globals.js"
import {fast1a52 as fnvFast1a52} from "fnv-plus"
import {parse as uuidParse, v1 as uuidV1, validate as uuidValidate, version as uuidVersion} from "uuid"
import {SnowflakeUtil} from "discord.js"
import {QueryType} from "./database.js"
export interface UsersTable {
getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string>
getActiveSnowflakeIsAdmin(snowflake: Snowflake): Promise<boolean>
createBotOwnerAsAdmin(snowflake: Snowflake): Promise<void>
}
export class UsersTableImpl implements UsersTable {
private readonly _query: QueryType
constructor({query}: { query: QueryType }) {
this._query = query
}
async getUuidForActiveSnowflake(snowflake: Snowflake): Promise<string> {
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())
ON CONFLICT (id) DO UPDATE SET active_at = now()
RETURNING id;`, [userSnowflakeToUuid(snowflake)])
return result.rows[0].id
}
async getActiveSnowflakeIsAdmin(snowflake: Snowflake): Promise<boolean> {
const result = await this._query<{ is_admin: boolean }, [string]>(
`UPDATE users
SET active_at = NOW()
WHERE id = $1
RETURNING is_admin;`, [userSnowflakeToUuid(snowflake)])
if (result.rowCount === 0) {
return false
}
return result.rows[0].is_admin
}
async createBotOwnerAsAdmin(snowflake: Snowflake): Promise<void> {
await this._query<{}, [string]>(
`INSERT INTO users (id, is_admin, created_at, updated_at)
VALUES ($1, TRUE, now(), now())
ON CONFLICT (id) DO UPDATE SET is_admin = TRUE,
updated_at = NOW()
WHERE users.is_admin = FALSE;`, [userSnowflakeToUuid(snowflake)])
}
}
export function userUuidToSnowflake(uuid: string): Snowflake | null {
if (!uuidValidate(uuid) || uuidVersion(uuid) !== 1) {
return null
}
const bytes = uuidParse(uuid)
const time_low = bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]
const time_mid = bytes[4] << 8 | bytes[5]
const time_high = (bytes[6] & 0x0F) << 8 | bytes[7]
const timestamp = ((BigInt(time_high) << 48n) | (BigInt(time_mid) << 32n) | BigInt(time_low)) / 10000n
const increment = BigInt((bytes[8] & 0x0F) << 8 | bytes[9])
const workerId = BigInt((bytes[14] & 0x03) << 3 | (bytes[15] & 0x07) >> 5)
const processId = BigInt(bytes[15] & 0x1F)
return SnowflakeUtil.generate({
timestamp,
increment,
workerId,
processId,
}).toString(10)
}
export function userSnowflakeToUuid(snowflake: Snowflake): string {
const {
timestamp,
increment,
workerId,
processId,
} = SnowflakeUtil.deconstruct(snowflake)
// Grab 52 hash bits for filling the empty spaces in the UUID.
const hashCode = fnvFast1a52(snowflake)
// We get 10000 possibilities for number of 100-nanosecond periods.
const nanoseconds = (hashCode % 10000)
// The node ID is 48 bits, and we have 10 from the process and worker IDs.
// The first bit must be 1 to indicate a made up node name, but the remaining 37 are open.
// Grab 37 bits after removing the 10000 variants of the 100-nanosecond periods.
const nodeIdPadding = Math.floor(hashCode / 10000) & 0x1FFFFFFFFF
const nodeId = new Uint8Array(6)
// Set the highest bit, indicating a multicast address...
// ... indicating an invalid address, indicating a made up node name.
// Then insert the hash padding and worker/process IDs.
// It doesn't matter what order they're in, as long as it's consistent.
// In this case, we choose the padding to be most significant, then worker, then process.
nodeId[0] = 0x80 | ((nodeIdPadding >> 30) & 0x7F)
nodeId[1] = (nodeIdPadding >> 22) & 0xFF
nodeId[2] = (nodeIdPadding >> 14) & 0xFF
nodeId[3] = (nodeIdPadding >> 6) & 0xFF
nodeId[4] = ((nodeIdPadding & 0x3F) << 2) | Number((workerId >> 3n) & 0x03n)
nodeId[5] = Number(((workerId & 0x07n) << 5n) | (processId & 0x1Fn))
return uuidV1({
rng: () => {
throw Error("rng is not supposed to be invoked")
},
// The snowflake increment is 12 bits.
// UUIDs use a 12 bit clock sequence number. Perfect fit!
clockseq: Number(increment & 0xFFFn),
nsecs: nanoseconds,
msecs: Number(timestamp & 0x3FFFFFFFFFFn),
node: nodeId,
})
}