311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
check,
|
|
index,
|
|
integer,
|
|
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 groups = pgTable(
|
|
"groups",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 50 }).notNull(),
|
|
slug: varchar("slug", { length: 50 }).notNull(),
|
|
description: text("description"),
|
|
accessEnabled: boolean("access_enabled").notNull().default(false),
|
|
anonymizedNetworksAllowed: boolean("anonymized_networks_allowed").notNull().default(false),
|
|
isDefault: boolean("is_default").notNull().default(false),
|
|
...timestamps(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("groups_slug_uidx").on(sql`lower(${table.slug})`),
|
|
uniqueIndex("groups_one_default_uidx").on(table.isDefault).where(sql`${table.isDefault} = true`),
|
|
],
|
|
);
|
|
|
|
export const groupAccessWindows = pgTable(
|
|
"group_access_windows",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
groupId: uuid("group_id")
|
|
.notNull()
|
|
.references(() => groups.id, { onDelete: "cascade" }),
|
|
startMinuteOfWeek: integer("start_minute_of_week").notNull(),
|
|
endMinuteOfWeek: integer("end_minute_of_week").notNull(),
|
|
createdAt: createdAt(),
|
|
},
|
|
(table) => [
|
|
index("group_access_windows_group_idx").on(table.groupId),
|
|
check(
|
|
"group_access_windows_minute_range_check",
|
|
sql`${table.startMinuteOfWeek} >= 0 and ${table.startMinuteOfWeek} < 10080 and ${table.endMinuteOfWeek} >= 0 and ${table.endMinuteOfWeek} < 10080 and ${table.startMinuteOfWeek} <> ${table.endMinuteOfWeek}`,
|
|
),
|
|
],
|
|
);
|
|
|
|
export const userGroupMemberships = pgTable(
|
|
"user_group_memberships",
|
|
{
|
|
userId: uuid("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
groupId: uuid("group_id")
|
|
.notNull()
|
|
.references(() => groups.id, { onDelete: "cascade" }),
|
|
createdAt: createdAt(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("user_group_memberships_user_uidx").on(table.userId),
|
|
index("user_group_memberships_group_idx").on(table.groupId),
|
|
],
|
|
);
|
|
|
|
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"),
|
|
registrationMessage: text("registration_message")
|
|
.notNull()
|
|
.default("Please register your Minecraft account before joining."),
|
|
groupAccessDeniedMessage: text("group_access_denied_message")
|
|
.notNull()
|
|
.default("Your account group does not currently have server access. Contact a host if you believe this is a mistake."),
|
|
vpnDeniedMessage: text("vpn_denied_message")
|
|
.notNull()
|
|
.default("VPN, proxy, and Tor connections are not allowed. Contact a host to request an exception."),
|
|
scheduledAccessDeniedMessage: text("scheduled_access_denied_message")
|
|
.notNull()
|
|
.default("Your group is only allowed access from {next_start} to {next_end}."),
|
|
...timestamps(),
|
|
});
|
|
|
|
export const rconServers = pgTable(
|
|
"rcon_servers",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 100 }).notNull(),
|
|
host: varchar("host", { length: 253 }).notNull(),
|
|
port: integer("port").notNull().default(25575),
|
|
encryptedPassword: text("encrypted_password").notNull(),
|
|
enabled: boolean("enabled").notNull().default(false),
|
|
...timestamps(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("rcon_servers_name_uidx").on(sql`lower(${table.name})`),
|
|
check("rcon_servers_port_check", sql`${table.port} between 1 and 65535`),
|
|
],
|
|
);
|
|
|
|
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 pluginRequests = pgTable(
|
|
"plugin_requests",
|
|
{
|
|
requestId: uuid("request_id").primaryKey(),
|
|
serverId: text("server_id")
|
|
.notNull()
|
|
.references(() => pluginCredentials.serverId, { onDelete: "cascade" }),
|
|
receivedAt: timestamp("received_at", { withTimezone: true, mode: "date", precision: 3 })
|
|
.notNull()
|
|
.defaultNow(),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date", precision: 3 }).notNull(),
|
|
},
|
|
(table) => [index("plugin_requests_expires_idx").on(table.expiresAt)],
|
|
);
|
|
|
|
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`),
|
|
],
|
|
);
|