diff --git a/package.json b/package.json index 573decf..88b7ce9 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", "autoprefixer": "^10.4.19", - "dotenv": "^16.4.5", "drizzle-kit": "^0.22.7", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -90,6 +89,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", "drizzle-orm": "^0.31.2", "feather-icons": "^4.29.2", "formsnap": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f52d9d..91c36a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,12 @@ importers: cookie: specifier: ^0.6.0 version: 0.6.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + dotenv-expand: + specifier: ^11.0.6 + version: 11.0.6 drizzle-orm: specifier: ^0.31.2 version: 0.31.2(@neondatabase/serverless@0.9.3)(@types/pg@8.11.6)(pg@8.12.0)(postgres@3.4.4) @@ -162,9 +168,6 @@ importers: autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) - dotenv: - specifier: ^16.4.5 - version: 16.4.5 drizzle-kit: specifier: ^0.22.7 version: 0.22.7 @@ -2371,6 +2374,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv-expand@11.0.6: + resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==} + engines: {node: '>=12'} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -6058,6 +6065,10 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv-expand@11.0.6: + dependencies: + dotenv: 16.4.5 + dotenv@16.4.5: {} drizzle-kit@0.22.7: diff --git a/src/db/index.ts b/src/db/index.ts index 37c318d..8715a69 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,30 +1,22 @@ import { drizzle } from 'drizzle-orm/node-postgres'; import pg from 'pg'; -import { - DATABASE_USER, - DATABASE_PASSWORD, - DATABASE_HOST, - DATABASE_DB, - DATABASE_PORT, - DB_MIGRATING, - DB_SEEDING, -} from '$env/static/private'; +import env from '../env'; import * as schema from './schema'; // create the connection -const pool = new pg.Pool({ - user: DATABASE_USER, - password: DATABASE_PASSWORD, - host: DATABASE_HOST, - port: Number(DATABASE_PORT).valueOf(), - database: DATABASE_DB, - ssl: DATABASE_HOST !== 'localhost', - max: DB_MIGRATING || DB_SEEDING ? 1 : undefined, +export const pool = new pg.Pool({ + user: env.DATABASE_USER, + password: env.DATABASE_PASSWORD, + host: env.DATABASE_HOST, + port: Number(env.DATABASE_PORT).valueOf(), + database: env.DATABASE_DB, + ssl: env.DATABASE_HOST !== 'localhost', + max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined, }); -const db = drizzle(pool, { +export const db = drizzle(pool, { schema, - logger: process.env.NODE_ENV === 'development', + logger: env.NODE_ENV === 'development', }); export type db = typeof db; diff --git a/src/db/migrate.ts b/src/db/migrate.ts index 47060da..b891b47 100644 --- a/src/db/migrate.ts +++ b/src/db/migrate.ts @@ -1,16 +1,17 @@ import 'dotenv/config'; import postgres from 'postgres'; -import config from '../../drizzle.config'; import { drizzle } from 'drizzle-orm/postgres-js'; import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import env from '../env'; +import config from '../../drizzle.config'; const connection = postgres({ - host: process.env.DATABASE_HOST || 'localhost', - port: process.env.DATABASE_PORT, - user: process.env.DATABASE_USER || 'root', - password: process.env.DATABASE_PASSWORD || '', - database: process.env.DATABASE_DB || 'boredgame', - ssl: process.env.NODE_ENV === 'development' ? false : 'require', + host: env.DATABASE_HOST || 'localhost', + port: env.DATABASE_PORT, + user: env.DATABASE_USER || 'root', + password: env.DATABASE_PASSWORD || '', + database: env.DATABASE_DB || 'boredgame', + ssl: env.NODE_ENV === 'development' ? false : 'require', max: 1, }); const db = drizzle(connection); diff --git a/src/db/schema/expansions.ts b/src/db/schema/expansions.ts index f29f83d..c8162e3 100644 --- a/src/db/schema/expansions.ts +++ b/src/db/schema/expansions.ts @@ -30,3 +30,5 @@ export const expansion_relations = relations(expansions, ({ one }) => ({ references: [games.id], }), })); + +export default expansions; diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts index 7e64ec8..824c32b 100644 --- a/src/db/schema/index.ts +++ b/src/db/schema/index.ts @@ -23,7 +23,7 @@ export { export { default as externalIds, type ExternalIds, type ExternalIdType } from './externalIds'; export { default as games, gameRelations, type Games } from './games'; export { default as gamesToExternalIds } from './gamesToExternalIds'; -export { default as expansions, expansions_relations, type Expansions } from './expansions'; +export { default as expansions, expansion_relations, type Expansions } from './expansions'; export { default as publishers, publishers_relations, type Publishers } from './publishers'; export { default as publishers_to_games, publishers_to_games_relations } from './publishersToGames'; export { default as publishersToExternalIds } from './publishersToExternalIds'; diff --git a/src/db/seed.ts b/src/db/seed.ts index 479eb87..98be190 100644 --- a/src/db/seed.ts +++ b/src/db/seed.ts @@ -1,11 +1,10 @@ import { Table, getTableName, sql } from 'drizzle-orm'; -import { Argon2id } from 'oslo/password'; -import { ADMIN_USERNAME, DB_SEEDING } from '$env/static/private'; -import { db } from '../db'; +import env from '../env'; +import { db, pool } from '$db'; import * as schema from './schema'; import * as seeds from './seeds'; -if (!DB_SEEDING) { +if (!env.DB_SEEDING) { throw new Error('You must set DB_SEEDING to "true" when running seeds'); } @@ -43,66 +42,7 @@ for (const table of [ } await seeds.roles(db); - -await connection.end(); - -console.log('Creating roles ...'); -const adminRole = await db - .insert(schema.roles) - .values([{ name: 'admin' }]) - .onConflictDoNothing() - .returning(); -const userRole = await db - .insert(schema.roles) - .values([{ name: 'user' }]) - .onConflictDoNothing() - .returning(); -await db - .insert(schema.roles) - .values([{ name: 'editor' }]) - .onConflictDoNothing(); -await db - .insert(schema.roles) - .values([{ name: 'moderator' }]) - .onConflictDoNothing(); -console.log('Roles created.'); - -console.log('Admin Role: ', adminRole); - -const adminUser = await db - .insert(schema.users) - .values({ - username: `${ADMIN_USERNAME}`, - email: '', - hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`), - first_name: 'Brad', - last_name: 'S', - verified: true, - }) - .returning() - .onConflictDoNothing(); - -console.log('Admin user created.', adminUser); - -await db - .insert(schema.user_roles) - .values({ - user_id: adminUser[0].id, - role_id: adminRole[0].id, - }) - .onConflictDoNothing(); - -console.log('Admin user given admin role.'); - -await db - .insert(schema.user_roles) - .values({ - user_id: adminUser[0].id, - role_id: userRole[0].id, - }) - .onConflictDoNothing(); - -console.log('Admin user given user role.'); +await seeds.users(db); await pool.end(); process.exit(); diff --git a/src/db/seeds/data/roles.json b/src/db/seeds/data/roles.json index e69de29..3209c58 100644 --- a/src/db/seeds/data/roles.json +++ b/src/db/seeds/data/roles.json @@ -0,0 +1,14 @@ +[ + { + "name": "admin" + }, + { + "name": "user" + }, + { + "name": "editor" + }, + { + "name": "moderator" + } +] \ No newline at end of file diff --git a/src/db/seeds/data/users.json b/src/db/seeds/data/users.json index 64dd254..61fa25d 100644 --- a/src/db/seeds/data/users.json +++ b/src/db/seeds/data/users.json @@ -4,20 +4,59 @@ "last_name": "Smith", "username": "john.smith", "email": "john.smith@example.com", - "password": "password" + "password": "password", + "roles": [ + { + "name": "user", + "primary": true + } + ] }, { "first_name": "Jane", "last_name": "Doe", "username": "jane.doe", "email": "jane.doe@example.com", - "password": "password" + "password": "password", + "roles": [ + { + "name": "user", + "primary": true + } + ] }, { "first_name": "Michael", - "last_name": "Jones", - "username": "michael.jones", - "email": "michael.jones@example.com", - "password": "password" + "last_name": "Editor", + "username": "michael.editor", + "email": "michael.editor@example.com", + "password": "password", + "roles": [ + { + "name": "editor", + "primary": true + }, + { + "name": "user", + "primary": false + } + ] + }, + { + "first_name": "Jane", + "last_name": "Moderator", + "username": "jane.moderator", + "email": "jane.moderator@example.com", + "password": "password", + "roles": [ + { + "name": "moderator", + "primary": true + }, + { + "name": "user", + "primary": false + } + ] } ] \ No newline at end of file diff --git a/src/db/seeds/index.ts b/src/db/seeds/index.ts new file mode 100644 index 0000000..0a05205 --- /dev/null +++ b/src/db/seeds/index.ts @@ -0,0 +1,2 @@ +export { default as users } from './users'; +export { default as roles } from './roles'; diff --git a/src/db/seeds/roles.ts b/src/db/seeds/roles.ts index 485afac..04d1724 100644 --- a/src/db/seeds/roles.ts +++ b/src/db/seeds/roles.ts @@ -1,30 +1,12 @@ import { eq } from 'drizzle-orm'; -import type db from '@/db'; -import * as schema from '@/db/schema'; +import { type db } from '$db'; +import * as schema from '$db/schema'; import roles from './data/roles.json'; -// console.log('Creating roles ...'); -// const adminRole = await db -// .insert(schema.roles) -// .values([{ name: 'admin' }]) -// .onConflictDoNothing() -// .returning(); -// const userRole = await db -// .insert(schema.roles) -// .values([{ name: 'user' }]) -// .onConflictDoNothing() -// .returning(); -// await db -// .insert(schema.roles) -// .values([{ name: 'editor' }]) -// .onConflictDoNothing(); -// await db -// .insert(schema.roles) -// .values([{ name: 'moderator' }]) -// .onConflictDoNothing(); -// console.log('Roles created.'); - export default async function seed(db: db) { - await db.insert(schema.roles).values({ name: 'user' }); - await db.insert(schema.roles).values({ name: 'admin' }); + console.log('Creating roles ...'); + for (const role of roles) { + await db.insert(schema.roles).values(role).onConflictDoNothing(); + } + console.log('Roles created.'); } diff --git a/src/db/seeds/users.ts b/src/db/seeds/users.ts index 734cb2b..65e8a21 100644 --- a/src/db/seeds/users.ts +++ b/src/db/seeds/users.ts @@ -1,38 +1,85 @@ import { eq } from 'drizzle-orm'; -import * as argon2 from 'argon2'; -import type db from '@/db'; -import * as schema from '@/db/schema'; +import { Argon2id } from 'oslo/password'; +import { type db } from '$db'; +import * as schema from '$db/schema'; import users from './data/users.json'; +import env from '../../env'; -async function getCityId(db: db, cityName: string) { - const city = await db.query.city.findFirst({ - where: eq(schema.city.name, cityName), - }); - if (!city) { - throw new Error('Unknown city name: ' + cityName); - } - return city.id; -} +type JsonUser = { + id: string; + username: string; + email: string; + password: string; + roles: { + name: string; + primary: boolean; + }[]; +}; + +type JsonRole = { + name: string; + primary: boolean; +}; export default async function seed(db: db) { + const adminRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'admin')); + const userRole = await db.select().from(schema.roles).where(eq(schema.roles.name, 'user')); + + console.log('Admin Role: ', adminRole); + const adminUser = await db + .insert(schema.users) + .values({ + username: `${env.ADMIN_USERNAME}`, + email: '', + hashed_password: await new Argon2id().hash(`${env.ADMIN_PASSWORD}`), + first_name: 'Brad', + last_name: 'S', + verified: true, + }) + .returning() + .onConflictDoNothing(); + + console.log('Admin user created.', adminUser); + + await db + .insert(schema.userRoles) + .values({ + user_id: adminUser[0].id, + role_id: adminRole[0].id, + }) + .onConflictDoNothing(); + + console.log('Admin user given admin role.'); + + await db + .insert(schema.userRoles) + .values({ + user_id: adminUser[0].id, + role_id: userRole[0].id, + }) + .onConflictDoNothing(); + + console.log('Admin user given user role.'); await Promise.all( users.map(async (user) => { const [insertedUser] = await db - .insert(schema.user) + .insert(schema.users) .values({ ...user, - emailVerified: true, - phoneVerified: true, - password: await argon2.hash(user.password), + hashed_password: await new Argon2id().hash(user.password), }) .returning(); + await db.insert(schema.collections).values({ user_id: insertedUser?.id }); + await db.insert(schema.wishlists).values({ user_id: insertedUser?.id }); await Promise.all( - user.addresses.map(async (address) => { - await db.insert(schema.address).values({ - ...address, - streetAddress1: address.street_address, - userId: insertedUser.id, - cityId: await getCityId(db, address.city), + user.roles.map(async (role: JsonRole) => { + const foundRole = await db.query.roles.findFirst({ + where: eq(schema.roles.name, role.name), + }); + await db.insert(schema.userRoles).values({ + user_id: insertedUser?.id, + role_id: foundRole?.id, + primary: role?.primary, }); }), ); diff --git a/src/env.ts b/src/env.ts index a7fcda5..e00486b 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,3 +1,5 @@ +import { config } from 'dotenv'; +import { expand } from 'dotenv-expand'; import { ZodError, z } from 'zod'; const stringBoolean = z.coerce @@ -21,10 +23,14 @@ const EnvSchema = z.object({ PUBLIC_UMAMI_URL: z.string(), DB_MIGRATING: stringBoolean, DB_SEEDING: stringBoolean, + ADMIN_USERNAME: z.string(), + ADMIN_PASSWORD: z.string(), }); export type EnvSchema = z.infer; +expand(config()); + try { EnvSchema.parse(process.env); } catch (error) {