Tovább a tartalomhoz

Adatbázis

Az ElyOS PostgreSQL adatbázist használ Drizzle ORM-mel. A séma a packages/database csomagban él, a repository-k az apps/web/src/lib/server/database/repositories/ mappában.

packages/database/src/schemas/
├── auth/ # better-auth táblák
│ ├── users/ # Felhasználók
│ ├── authentication/ # Munkamenetek, tokenek
│ ├── groups/ # Csoportok
│ ├── roles/ # Szerepkörök
│ ├── permissions/ # Jogosultságok
│ └── audit/ # Audit napló
└── platform/ # Platform táblák
├── apps/ # Alkalmazás regisztráció
├── chat/ # Chat üzenetek
├── desktop/ # Asztali konfiguráció
├── i18n/ # Fordítások
├── logging/ # Rendszernapló
├── notifications/ # Értesítések
├── plugins/ # Plugin metaadatok
├── settings/ # Felhasználói beállítások
└── files/ # Fájl metaadatok
import { db, schema } from '@elyos/database';
import { db, schema } from '@elyos/database';
import { eq, and, like, desc, asc } from 'drizzle-orm';
// Összes rekord
const users = await db.select().from(schema.users);
// Szűréssel
const activeUsers = await db
.select()
.from(schema.users)
.where(eq(schema.users.isActive, true));
// Több feltétel
const result = await db
.select()
.from(schema.users)
.where(
and(
eq(schema.users.isActive, true),
like(schema.users.name, '%admin%')
)
)
.orderBy(desc(schema.users.createdAt))
.limit(20)
.offset(0);
// Egy rekord
const user = await db
.select()
.from(schema.users)
.where(eq(schema.users.id, userId))
.then(rows => rows[0] ?? null);
const [newUser] = await db
.insert(schema.users)
.values({
name: 'Teszt Felhasználó',
email: 'teszt@example.com',
isActive: true
})
.returning();
const [updated] = await db
.update(schema.users)
.set({ name: 'Új Név', updatedAt: new Date() })
.where(eq(schema.users.id, userId))
.returning();
await db
.delete(schema.users)
.where(eq(schema.users.id, userId));
const usersWithGroups = await db
.select({
userId: schema.users.id,
userName: schema.users.name,
groupName: schema.groups.name
})
.from(schema.users)
.leftJoin(
schema.userGroups,
eq(schema.users.id, schema.userGroups.userId)
)
.leftJoin(
schema.groups,
eq(schema.userGroups.groupId, schema.groups.id)
);

Az adatbázis műveletek repository osztályokban vannak szervezve. Minden repository egy adott entitáshoz tartozó CRUD és üzleti logikát tartalmaz.

src/lib/server/database/repositories/user-repository.ts
import { db, schema } from '@elyos/database';
import { eq, and, like, desc, count } from 'drizzle-orm';
export class UserRepository {
async findById(id: number) {
return db
.select()
.from(schema.users)
.where(eq(schema.users.id, id))
.then(rows => rows[0] ?? null);
}
async findManyPaginated(params: {
limit: number;
offset: number;
search?: string;
}) {
const conditions = [];
if (params.search) {
conditions.push(like(schema.users.name, `%${params.search}%`));
}
return db
.select()
.from(schema.users)
.where(conditions.length ? and(...conditions) : undefined)
.orderBy(desc(schema.users.createdAt))
.limit(params.limit)
.offset(params.offset);
}
async countAll(params: { search?: string } = {}) {
const conditions = [];
if (params.search) {
conditions.push(like(schema.users.name, `%${params.search}%`));
}
const [result] = await db
.select({ count: count() })
.from(schema.users)
.where(conditions.length ? and(...conditions) : undefined);
return result?.count ?? 0;
}
}
// Singleton export
export const userRepository = new UserRepository();
import { userRepository } from '$lib/server/database/repositories';
ExportLeírás
userRepositoryFelhasználók kezelése
groupRepositoryCsoportok kezelése
roleRepositorySzerepkörök kezelése
permissionRepositoryJogosultságok kezelése
appRepositoryAlkalmazás regisztráció
notificationRepositoryÉrtesítések
translationRepositoryFordítások (i18n)
themePresetsRepositoryTéma presetek

Új tábla hozzáadásakor a packages/database/src/schemas/platform/ mappában kell dolgozni:

packages/database/src/schemas/platform/items/schema.ts
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core';
export const items = pgTable('items', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
description: text('description'),
isActive: boolean('is_active').notNull().default(true),
userId: integer('user_id').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow()
});
packages/database/src/schemas/platform/relations.ts
import { relations } from 'drizzle-orm';
import { items } from './items/schema';
import { users } from '../auth/users/schema';
export const itemsRelations = relations(items, ({ one }) => ({
user: one(users, {
fields: [items.userId],
references: [users.id]
})
}));

Sémaváltozás után mindig generálj migrációt:

Terminál
# Migráció generálása
bun db:generate
# Migráció futtatása
bun db:migrate
# Vizuális ellenőrzés
bun db:studio

A seed adatok kezdeti adatokat töltenek be az adatbázisba — alapértelmezett felhasználók, szerepkörök, alkalmazások, fordítások stb. Az ElyOS seed rendszere idempotens (biztonságosan futtatható többször is) és függőség-alapú (automatikus sorrendezés).

packages/database/src/seeds/
├── config.ts # Seed definíciók és függőségek
├── runner.ts # Seed futtatási logika
├── init-db.ts # Teljes adatbázis inicializálás
├── reset.ts # Teljes adatbázis reset (Docker)
├── demo-reset.ts # Demo környezet reset
├── sql/ # SQL seed fájlok
│ ├── auth/ # Auth séma seed-ek
│ │ ├── users.sql
│ │ ├── roles.sql
│ │ ├── groups.sql
│ │ ├── permissions.sql
│ │ └── ...
│ └── platform/ # Platform séma seed-ek
│ ├── apps.sql
│ ├── locales.sql
│ ├── translations_*.sql
│ └── ...
└── procedures/ # Stored procedure-ök
└── auth/
├── getGroups.sql
└── ...

A config.ts fájl definiálja az összes seed-et és azok függőségeit:

export const seedConfig: Record<string, SeedDefinition> = {
// Nincs függőség
roles: {
file: 'auth/roles.sql',
dependsOn: [],
description: 'User roles'
},
// Függ a roles seed-től
role_permissions: {
file: 'auth/role_permissions.sql',
dependsOn: ['roles', 'permissions'],
description: 'Role-permission assignments'
}
};

A seed runner automatikusan topológiai rendezést végez, így a függőségek mindig a megfelelő sorrendben futnak.

Terminál
# Teljes adatbázis inicializálás (séma + migráció + seed)
bun db:init
# Csak seed-ek futtatása (idempotens, nem truncate-el)
bun db:seed
# Teljes reset (truncate + seed)
bun db:reset
# Csak bizonyos seed-ek futtatása (truncate nélkül)
bun db:seed --no-truncate --only=users,roles,apps
# Csak bizonyos procedure-ök futtatása
bun db:seed --no-truncate --only-procedures=get_groups,get_groups_2

Minden seed fájl ON CONFLICT DO UPDATE logikát használ, így biztonságosan futtatható többször is:

-- auth/roles.sql
INSERT INTO auth.roles (id, name, description) VALUES
(1, '{"hu": "Rendszergazda", "en": "System Administrator"}',
'{"hu": "Korlátlan jogosultsággal rendelkező szerep", "en": "Role with unlimited privileges"}'),
(2, '{"hu": "Adminisztrátor", "en": "Administrator"}',
'{"hu": "Adminisztrációs feladatok elvégzésére jogosult szerep", "en": "Role authorized for administrative tasks"}')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description;
-- Szekvencia frissítése a legnagyobb id alapján
SELECT setval('auth.roles_id_seq', (SELECT COALESCE(MAX(id), 0) FROM auth.roles));
SeedLeírásFüggőségek
resourcesErőforrás definíciók (permission system)-
providersAuth provider-ek (email, Google)-
groupsFelhasználói csoportok-
rolesSzerepkörök (admin, user, stb.)-
permissionsJogosultságokresources
role_permissionsSzerepkör-jogosultság hozzárendelésekroles, permissions
group_permissionsCsoport-jogosultság hozzárendelésekgroups, permissions
usersKezdeti felhasználók (admin)-
accountsAuth fiókokusers, providers
user_rolesFelhasználó-szerepkör hozzárendelésekusers, roles
user_groupsFelhasználó-csoport hozzárendelésekusers, groups
role_app_accessSzerepkör-alkalmazás hozzáférésapps, roles
group_app_accessCsoport-alkalmazás hozzáférésapps, groups
SeedLeírásFüggőségek
localesTámogatott nyelvek (hu, en)-
translations_commonKözös fordítások (gombok, státuszok)locales
translations_settingsBeállítások app fordításoklocales
translations_logNapló app fordításoklocales
translations_desktopDesktop környezet fordításoklocales
translations_authAuth oldalak fordításoklocales
translations_userFelhasználók app fordításoklocales
translations_notificationsÉrtesítési rendszer fordításoklocales
translations_plugin_managerPlugin Manager fordításoklocales
appsAlkalmazás regisztráció (metadata)-
email_templatesEmail sablonok (HU/EN)locales
theme_presetsTéma preseteklocales

A users.sql seed létrehoz egy rendszergazda felhasználót:

INSERT INTO auth.users (id, full_name, email, email_verified, username, image, user_settings, oauth_image) VALUES
(1, 'ElyOS admin', 'youradminemail@eyoursomain.com', true, null, null, '{}', null)
ON CONFLICT (id) DO UPDATE SET
full_name = EXCLUDED.full_name,
email_verified = EXCLUDED.email_verified;

Az admin email cím futásidőben felülírható a ADMIN_USER_EMAIL környezeti változóval:

.env
ADMIN_USER_EMAIL=admin@example.com

A seed runner automatikusan frissíti az admin user email címét:

runner.ts
async function applyAdminEmail() {
const adminEmail = process.env.ADMIN_USER_EMAIL?.trim();
if (!adminEmail) return;
await pool.query(
`UPDATE auth.users SET email = $1 WHERE id = (SELECT id FROM auth.users ORDER BY id ASC LIMIT 1)`,
[adminEmail]
);
}
  1. Hozd létre az SQL fájlt:
-- packages/database/src/seeds/sql/platform/my_data.sql
INSERT INTO platform.my_table (id, name, value) VALUES
(1, 'Item 1', 'Value 1'),
(2, 'Item 2', 'Value 2')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
value = EXCLUDED.value;
SELECT setval('platform.my_table_id_seq', (SELECT COALESCE(MAX(id), 0) FROM platform.my_table));
  1. Regisztráld a config.ts fájlban:
export const seedConfig: Record<string, SeedDefinition> = {
// ... meglévő seed-ek
my_data: {
file: 'platform/my_data.sql',
dependsOn: ['locales'], // ha van függőség
description: 'My custom data'
}
};
  1. Frissítsd a truncate sorrendet (ha szükséges):
export const truncateOrder = [
// ... meglévő táblák
'platform.my_table',
// ...
];
  1. Futtasd a seed-et:
Terminál
bun db:seed

A seed rendszer támogatja stored procedure-ök létrehozását is:

config.ts
export const procedureConfig: Record<string, ProcedureDefinition> = {
get_groups: {
file: 'auth/getGroups.sql',
description: 'Get groups by ID'
}
};
-- procedures/auth/getGroups.sql
CREATE OR REPLACE FUNCTION auth.get_groups(group_ids INTEGER[])
RETURNS TABLE (
id INTEGER,
name JSONB,
description JSONB
) AS $$
BEGIN
RETURN QUERY
SELECT g.id, g.name, g.description
FROM auth.groups g
WHERE g.id = ANY(group_ids);
END;
$$ LANGUAGE plpgsql;

A Docker Compose automatikusan futtatja a seed-eket a db-init konténerben:

db-init:
command: >
sh -c 'bun --filter @elyos/database db:init ${RESET:+-- --reset}'
depends_on:
postgres:
condition: service_healthy

Normál indítás (idempotens):

Terminál
bun docker:up

Teljes reset (truncate + seed):

Terminál
RESET=1 bun docker:up

A db:init parancs a következő lépéseket hajtja végre:

  1. PostgreSQL elérhetőség ellenőrzése — health check
  2. Sémák létrehozásaauth, platform, extensions
  3. PostgreSQL extensionök engedélyezésepostgres-json-schema
  4. Drizzle migrációk futtatása — séma alkalmazása
  5. Seed-ek futtatása — függőségi sorrendben
  6. Stored procedure-ök létrehozása — ha vannak
  7. Admin email frissítése — ha ADMIN_USER_EMAIL meg van adva
Terminál
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Database Initialization
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔌 Connecting to PostgreSQL...
PostgreSQL is ready
🏗️ Creating database schemas...
Schema "auth" ready
Schema "platform" ready
Schema "extensions" ready
🔌 Enabling PostgreSQL extensions...
Extension "postgres-json-schema" enabled
📦 Applying database schema...
Migrations completed
🌱 Seeding database...
resources - Resource definitions
providers - Authentication providers
groups - User groups
roles - User roles
permissions - Permissions linked to resources
users - Initial users
locales - Supported locales
apps - Application registry
... (további seed-ek)
⚙️ Creating stored procedures...
get_groups - Get groups by ID
👤 Admin email beállítása: admin@example.com
Admin user email frissítve
🎉 Database initialization completed successfully!
  1. Mindig használj upsert logikátON CONFLICT DO UPDATE
  2. Frissítsd a szekvenciákatSELECT setval(...)
  3. Definiálj függőségeket — a dependsOn tömbben
  4. Használj leíró neveketdescription mező
  5. Tesztelj idempotenciát — futtasd többször is
  6. Dokumentáld az adatokat — SQL kommentekkel
  7. Használj JSONB-t többnyelvű adatokhoz{"hu": "...", "en": "..."}

Probléma: Seed hiba — duplicate key value violates unique constraint

Megoldás: Ellenőrizd, hogy az ON CONFLICT klauzula megfelelően van-e beállítva.


Probléma: Szekvencia nem frissül — az auto-increment ütközik

Megoldás: Add hozzá a szekvencia frissítést a seed végére:

SELECT setval('schema.table_id_seq', (SELECT COALESCE(MAX(id), 0) FROM schema.table));

Probléma: Függőségi hiba — seed nem fut le a megfelelő sorrendben

Megoldás: Ellenőrizd a dependsOn tömböt a config.ts fájlban.

await db.transaction(async (tx) => {
const [user] = await tx
.insert(schema.users)
.values({ name: 'Új Felhasználó', email: 'uj@example.com' })
.returning();
await tx
.insert(schema.userGroups)
.values({ userId: user.id, groupId: defaultGroupId });
});
import { ensureDatabaseHealth } from '$lib/server/database/health';
// Ellenőrzi, hogy az adatbázis elérhető-e
await ensureDatabaseHealth();
import { validatePaginationParams } from '$lib/server/utils/database';
const { page, limit, offset } = validatePaginationParams(
input.page, // kért oldal (1-től)
input.pageSize // oldal méret
);