214 lines
7.5 KiB
TypeScript
214 lines
7.5 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
index,
|
|
inet,
|
|
jsonb,
|
|
pgEnum,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
uniqueIndex,
|
|
uuid,
|
|
varchar,
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
function timestamps() {
|
|
return {
|
|
createdAt: timestamp("created_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow(),
|
|
};
|
|
}
|
|
|
|
function createdAt() {
|
|
return timestamp("created_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow();
|
|
}
|
|
|
|
export const minecraftValidationStatus = pgEnum("minecraft_validation_status", [
|
|
"verified",
|
|
"user_confirmed",
|
|
]);
|
|
export const ipObservationSource = pgEnum("ip_observation_source", ["web", "game"]);
|
|
export const ipClassification = pgEnum("ip_classification", [
|
|
"unknown",
|
|
"clear",
|
|
"vpn",
|
|
"proxy",
|
|
"hosting",
|
|
"tor",
|
|
]);
|
|
|
|
export const users = pgTable(
|
|
"users",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
discordUserId: text("discord_user_id").notNull(),
|
|
discordUsername: text("discord_username").notNull(),
|
|
discordGlobalName: text("discord_global_name"),
|
|
firstName: text("first_name"),
|
|
onboardingCompletedAt: timestamp("onboarding_completed_at", {
|
|
withTimezone: true,
|
|
mode: "date",
|
|
precision: 3,
|
|
}),
|
|
...timestamps(),
|
|
},
|
|
(table) => [uniqueIndex("users_discord_user_id_uidx").on(table.discordUserId)],
|
|
);
|
|
|
|
export const minecraftAccounts = pgTable(
|
|
"minecraft_accounts",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
minecraftUuid: varchar("minecraft_uuid", { length: 32 }),
|
|
username: varchar("username", { length: 16 }).notNull(),
|
|
validationStatus: minecraftValidationStatus("validation_status").notNull(),
|
|
isPrimary: boolean("is_primary").notNull().default(false),
|
|
lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
deletedAt: timestamp("deleted_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
...timestamps(),
|
|
},
|
|
(table) => [
|
|
index("minecraft_accounts_user_idx").on(table.userId),
|
|
uniqueIndex("minecraft_accounts_active_uuid_uidx")
|
|
.on(table.minecraftUuid)
|
|
.where(sql`${table.deletedAt} is null and ${table.minecraftUuid} is not null`),
|
|
uniqueIndex("minecraft_accounts_active_username_uidx")
|
|
.on(sql`lower(${table.username})`)
|
|
.where(sql`${table.deletedAt} is null`),
|
|
uniqueIndex("minecraft_accounts_one_primary_per_user_uidx")
|
|
.on(table.userId)
|
|
.where(sql`${table.isPrimary} = true and ${table.deletedAt} is null`),
|
|
],
|
|
);
|
|
|
|
export const loginCodes = pgTable(
|
|
"login_codes",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
tokenHash: text("token_hash").notNull(),
|
|
discordUserId: text("discord_user_id").notNull(),
|
|
discordUsername: text("discord_username").notNull(),
|
|
discordGlobalName: text("discord_global_name"),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date", precision: 3 }).notNull(),
|
|
consumedAt: timestamp("consumed_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
createdAt: createdAt(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("login_codes_token_hash_uidx").on(table.tokenHash),
|
|
index("login_codes_discord_user_idx").on(table.discordUserId),
|
|
index("login_codes_expires_idx").on(table.expiresAt),
|
|
],
|
|
);
|
|
|
|
export const sessions = pgTable(
|
|
"sessions",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
tokenHash: text("token_hash").notNull(),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date", precision: 3 }).notNull(),
|
|
lastSeenAt: timestamp("last_seen_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow(),
|
|
revokedAt: timestamp("revoked_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
createdAt: createdAt(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("sessions_token_hash_uidx").on(table.tokenHash),
|
|
index("sessions_user_idx").on(table.userId),
|
|
index("sessions_expires_idx").on(table.expiresAt),
|
|
],
|
|
);
|
|
|
|
export const appSettings = pgTable("app_settings", {
|
|
id: text("id").primaryKey().default("default"),
|
|
discordGuildId: text("discord_guild_id"),
|
|
registrationMessage: text("registration_message")
|
|
.notNull()
|
|
.default("Please register your Minecraft account before joining."),
|
|
...timestamps(),
|
|
});
|
|
|
|
export const ipIntelligence = pgTable("ip_intelligence", {
|
|
ipAddress: inet("ip_address").primaryKey(),
|
|
classification: ipClassification("classification").notNull().default("unknown"),
|
|
provider: text("provider"),
|
|
rawResponse: jsonb("raw_response").$type<Record<string, unknown>>(),
|
|
checkedAt: timestamp("checked_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
...timestamps(),
|
|
});
|
|
|
|
export const ipObservations = pgTable(
|
|
"ip_observations",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
userId: uuid("user_id").references(() => users.id, { onDelete: "set null" }),
|
|
minecraftAccountId: uuid("minecraft_account_id").references(() => minecraftAccounts.id, {
|
|
onDelete: "set null",
|
|
}),
|
|
source: ipObservationSource("source").notNull(),
|
|
ipAddress: inet("ip_address").notNull(),
|
|
minecraftUuid: varchar("minecraft_uuid", { length: 32 }),
|
|
username: varchar("username", { length: 16 }),
|
|
classification: ipClassification("classification").notNull().default("unknown"),
|
|
observedAt: timestamp("observed_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("ip_observations_user_observed_idx").on(table.userId, table.observedAt),
|
|
index("ip_observations_account_observed_idx").on(table.minecraftAccountId, table.observedAt),
|
|
],
|
|
);
|
|
|
|
export const pluginCredentials = pgTable(
|
|
"plugin_credentials",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
serverId: text("server_id").notNull(),
|
|
secretHash: text("secret_hash").notNull(),
|
|
revokedAt: timestamp("revoked_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
...timestamps(),
|
|
},
|
|
(table) => [uniqueIndex("plugin_credentials_server_id_uidx").on(table.serverId)],
|
|
);
|
|
|
|
export const events = pgTable(
|
|
"events",
|
|
{
|
|
id: uuid("id").primaryKey(),
|
|
specVersion: text("spec_version").notNull().default("1.0"),
|
|
source: text("source").notNull(),
|
|
type: text("type").notNull(),
|
|
subject: text("subject"),
|
|
time: timestamp("time", { withTimezone: true, mode: "date", precision: 3 }).notNull(),
|
|
dataContentType: text("data_content_type").notNull().default("application/json"),
|
|
dataSchema: text("data_schema"),
|
|
data: jsonb("data").$type<Record<string, unknown>>().notNull(),
|
|
actorUserId: uuid("actor_user_id").references(() => users.id, { onDelete: "set null" }),
|
|
ipAddress: inet("ip_address"),
|
|
correlationId: uuid("correlation_id"),
|
|
publishedAt: timestamp("published_at", { withTimezone: true, mode: "date", precision: 3 }),
|
|
createdAt: createdAt(),
|
|
},
|
|
(table) => [
|
|
index("events_time_idx").on(table.time),
|
|
index("events_type_time_idx").on(table.type, table.time),
|
|
index("events_subject_time_idx").on(table.subject, table.time),
|
|
index("events_unpublished_idx").on(table.createdAt).where(sql`${table.publishedAt} is null`),
|
|
],
|
|
);
|