chore: Initial commit of Opencord base state
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { config } from '../config.js';
|
||||
import * as schema from './schema.js';
|
||||
import { mkdirSync } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
|
||||
let sqlite: Database.Database;
|
||||
|
||||
function ensureDirectory(filePath: string): void {
|
||||
const dir = dirname(filePath);
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function createTables(db: Database.Database): void {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
display_name TEXT,
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar TEXT,
|
||||
status TEXT DEFAULT 'offline',
|
||||
custom_status TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
icon TEXT,
|
||||
owner_id TEXT NOT NULL REFERENCES users(id),
|
||||
invite_code TEXT UNIQUE,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS server_members (
|
||||
server_id TEXT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role TEXT DEFAULT 'member',
|
||||
nickname TEXT,
|
||||
joined_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (server_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id TEXT PRIMARY KEY,
|
||||
server_id TEXT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
topic TEXT,
|
||||
position INTEGER DEFAULT 0,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id),
|
||||
content TEXT,
|
||||
edited_at INTEGER,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS attachments (
|
||||
id TEXT PRIMARY KEY,
|
||||
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
||||
filename TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
mimetype TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dm_channels (
|
||||
id TEXT PRIMARY KEY,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dm_members (
|
||||
dm_channel_id TEXT NOT NULL REFERENCES dm_channels(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (dm_channel_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dm_messages (
|
||||
id TEXT PRIMARY KEY,
|
||||
dm_channel_id TEXT NOT NULL REFERENCES dm_channels(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id),
|
||||
content TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
export function initDatabase() {
|
||||
ensureDirectory(config.dbPath);
|
||||
sqlite = new Database(config.dbPath);
|
||||
sqlite.pragma('journal_mode = WAL');
|
||||
sqlite.pragma('foreign_keys = ON');
|
||||
createTables(sqlite);
|
||||
console.log(`Database initialized at ${config.dbPath}`);
|
||||
return drizzle(sqlite, { schema });
|
||||
}
|
||||
|
||||
export type DB = ReturnType<typeof initDatabase>;
|
||||
|
||||
let db: DB;
|
||||
|
||||
export function getDb(): DB {
|
||||
if (!db) {
|
||||
db = initDatabase();
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export function closeDatabase(): void {
|
||||
if (sqlite) {
|
||||
sqlite.close();
|
||||
}
|
||||
}
|
||||
|
||||
export { schema };
|
||||
@@ -0,0 +1,80 @@
|
||||
import { sqliteTable, text, integer, primaryKey } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const users = sqliteTable('users', {
|
||||
id: text('id').primaryKey(),
|
||||
username: text('username').unique().notNull(),
|
||||
displayName: text('display_name'),
|
||||
passwordHash: text('password_hash').notNull(),
|
||||
avatar: text('avatar'),
|
||||
status: text('status').default('offline'),
|
||||
customStatus: text('custom_status'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const servers = sqliteTable('servers', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
icon: text('icon'),
|
||||
ownerId: text('owner_id').notNull().references(() => users.id),
|
||||
inviteCode: text('invite_code').unique(),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const serverMembers = sqliteTable('server_members', {
|
||||
serverId: text('server_id').notNull().references(() => servers.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
role: text('role').default('member'),
|
||||
nickname: text('nickname'),
|
||||
joinedAt: integer('joined_at').notNull(),
|
||||
}, (table) => ({
|
||||
pk: primaryKey({ columns: [table.serverId, table.userId] }),
|
||||
}));
|
||||
|
||||
export const channels = sqliteTable('channels', {
|
||||
id: text('id').primaryKey(),
|
||||
serverId: text('server_id').notNull().references(() => servers.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
type: text('type').notNull(),
|
||||
topic: text('topic'),
|
||||
position: integer('position').default(0),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const messages = sqliteTable('messages', {
|
||||
id: text('id').primaryKey(),
|
||||
channelId: text('channel_id').notNull().references(() => channels.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id').notNull().references(() => users.id),
|
||||
content: text('content'),
|
||||
editedAt: integer('edited_at'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const attachments = sqliteTable('attachments', {
|
||||
id: text('id').primaryKey(),
|
||||
messageId: text('message_id').references(() => messages.id, { onDelete: 'cascade' }),
|
||||
filename: text('filename').notNull(),
|
||||
originalName: text('original_name').notNull(),
|
||||
mimetype: text('mimetype').notNull(),
|
||||
size: integer('size').notNull(),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const dmChannels = sqliteTable('dm_channels', {
|
||||
id: text('id').primaryKey(),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const dmMembers = sqliteTable('dm_members', {
|
||||
dmChannelId: text('dm_channel_id').notNull().references(() => dmChannels.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
}, (table) => ({
|
||||
pk: primaryKey({ columns: [table.dmChannelId, table.userId] }),
|
||||
}));
|
||||
|
||||
export const dmMessages = sqliteTable('dm_messages', {
|
||||
id: text('id').primaryKey(),
|
||||
dmChannelId: text('dm_channel_id').notNull().references(() => dmChannels.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id').notNull().references(() => users.id),
|
||||
content: text('content'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { getDb, schema } from './index.js';
|
||||
import { generateSnowflake } from '../utils/snowflake.js';
|
||||
import { hashPassword } from '../utils/auth.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export async function seedDatabase(): Promise<void> {
|
||||
const db = getDb();
|
||||
|
||||
const existingServers = db.select().from(schema.servers).all();
|
||||
if (existingServers.length > 0) {
|
||||
console.log('Database already has data, skipping seed');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Seeding database with default data...');
|
||||
|
||||
const adminId = generateSnowflake();
|
||||
const adminPasswordHash = await hashPassword('admin123');
|
||||
|
||||
db.insert(schema.users).values({
|
||||
id: adminId,
|
||||
username: 'admin',
|
||||
displayName: 'Admin',
|
||||
passwordHash: adminPasswordHash,
|
||||
status: 'offline',
|
||||
createdAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
const serverId = generateSnowflake();
|
||||
db.insert(schema.servers).values({
|
||||
id: serverId,
|
||||
name: 'Opencord',
|
||||
ownerId: adminId,
|
||||
inviteCode: 'opencord',
|
||||
createdAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
db.insert(schema.serverMembers).values({
|
||||
serverId: serverId,
|
||||
userId: adminId,
|
||||
role: 'owner',
|
||||
joinedAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
const generalChannelId = generateSnowflake();
|
||||
db.insert(schema.channels).values({
|
||||
id: generalChannelId,
|
||||
serverId: serverId,
|
||||
name: 'general',
|
||||
type: 'text',
|
||||
topic: 'General discussion',
|
||||
position: 0,
|
||||
createdAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
const voiceChannelId = generateSnowflake();
|
||||
db.insert(schema.channels).values({
|
||||
id: voiceChannelId,
|
||||
serverId: serverId,
|
||||
name: 'General Voice',
|
||||
type: 'voice',
|
||||
position: 1,
|
||||
createdAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
console.log('Database seeded successfully');
|
||||
console.log(` Default server: Opencord (invite code: opencord)`);
|
||||
console.log(` Admin user: admin / admin123`);
|
||||
}
|
||||
Reference in New Issue
Block a user