Moving drizzle into it's own DB folder and moving all the table types to separate files.

This commit is contained in:
Bradley Shellnut 2024-05-07 17:19:13 -07:00
parent 0b58b3ff8f
commit a8eed0d86d
98 changed files with 5648 additions and 6076 deletions

View file

@ -2,8 +2,8 @@ import 'dotenv/config';
import { defineConfig } from 'drizzle-kit'; import { defineConfig } from 'drizzle-kit';
export default defineConfig({ export default defineConfig({
schema: './src/schema.ts', schema: './src/db/schema/index.ts',
out: './drizzle', out: './src/db/migrations',
driver: 'pg', driver: 'pg',
dbCredentials: { dbCredentials: {
host: process.env.DATABASE_HOST || 'localhost', host: process.env.DATABASE_HOST || 'localhost',
@ -11,10 +11,10 @@ export default defineConfig({
user: process.env.DATABASE_USER, user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD, password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE || 'boredgame', database: process.env.DATABASE || 'boredgame',
ssl: process.env.DATABASE_HOST !== 'localhost' ssl: process.env.DATABASE_HOST !== 'localhost',
}, },
// Print all statements // Print all statements
verbose: true, verbose: true,
// Always as for confirmation // Always as for confirmation
strict: true strict: true,
}); });

View file

@ -16,8 +16,8 @@
"format": "prettier --plugin-search-dir . --write .", "format": "prettier --plugin-search-dir . --write .",
"site:update": "pnpm update -i -L", "site:update": "pnpm update -i -L",
"generate": "drizzle-kit generate:pg", "generate": "drizzle-kit generate:pg",
"migrate": "tsx ./src/migrate.ts", "migrate": "tsx src/db/migrate.ts",
"seed": "tsx ./src/seed.ts", "seed": "tsx src/db/seed.ts",
"push": "drizzle-kit push:pg" "push": "drizzle-kit push:pg"
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,11 @@ import {
DATABASE_PASSWORD, DATABASE_PASSWORD,
DATABASE_HOST, DATABASE_HOST,
DATABASE_DB, DATABASE_DB,
DATABASE_PORT DATABASE_PORT,
DB_MIGRATING,
DB_SEEDING,
} from '$env/static/private'; } from '$env/static/private';
import * as schema from '../schema'; import * as schema from './schema';
// create the connection // create the connection
const pool = new pg.Pool({ const pool = new pg.Pool({
@ -16,15 +18,15 @@ const pool = new pg.Pool({
host: DATABASE_HOST, host: DATABASE_HOST,
port: Number(DATABASE_PORT).valueOf(), port: Number(DATABASE_PORT).valueOf(),
database: DATABASE_DB, database: DATABASE_DB,
ssl: DATABASE_HOST !== 'localhost' ssl: DATABASE_HOST !== 'localhost',
max: DB_MIGRATING || DB_SEEDING ? 1 : undefined,
}); });
// user: DATABASE_USER, const db = drizzle(pool, {
// password: DATABASE_PASSWORD, schema,
// host: DATABASE_HOST, logger: process.env.NODE_ENV === 'development',
// port: Number(DATABASE_PORT).valueOf(), });
// database: DATABASE_DB
const db = drizzle(pool, { schema }); export type db = typeof db;
export default db; export default db;

View file

@ -0,0 +1,24 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import categories_to_games from './categoriesToGames';
const categories = pgTable('categories', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Categories = InferSelectModel<typeof categories>;
export const categories_relations = relations(categories, ({ many }) => ({
categories_to_games: many(categories_to_games),
categoriesToExternalIds: many(categoriesToExternalIds),
}));
export default categories;

View file

@ -0,0 +1,24 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import categories from './categories';
import externalIds from './externalIds';
const categoriesToExternalIds = pgTable(
'categories_to_external_ids',
{
categoryId: uuid('category_id')
.notNull()
.references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
categoriesToExternalIdsPkey: primaryKey({
columns: [table.categoryId, table.externalId],
}),
};
},
);
export default categoriesToExternalIds;

View file

@ -0,0 +1,36 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import categories from './categories';
import games from './games';
const categories_to_games = pgTable(
'categories_to_games',
{
category_id: uuid('category_id')
.notNull()
.references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
categoriesToGamesPkey: primaryKey({
columns: [table.category_id, table.game_id],
}),
};
},
);
export const categories_to_games_relations = relations(categories_to_games, ({ one }) => ({
category: one(categories, {
fields: [categories_to_games.category_id],
references: [categories.id],
}),
game: one(games, {
fields: [categories_to_games.game_id],
references: [games.id],
}),
}));
export default categories_to_games;

View file

@ -0,0 +1,36 @@
import { integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import collections from './collections';
import games from './games';
const collection_items = pgTable('collection_items', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
collection_id: uuid('collection_id')
.notNull()
.references(() => collections.id, { onDelete: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
times_played: integer('times_played').default(0),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type CollectionItems = InferSelectModel<typeof collection_items>;
export const collection_item_relations = relations(collection_items, ({ one }) => ({
collection: one(collections, {
fields: [collection_items.collection_id],
references: [collections.id],
}),
game: one(games, {
fields: [collection_items.game_id],
references: [games.id],
}),
}));
export default collection_items;

View file

@ -0,0 +1,28 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
const collections = pgTable('collections', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Collection'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const collection_relations = relations(collections, ({ one }) => ({
user: one(users, {
fields: [collections.user_id],
references: [users.id],
}),
}));
export type Collections = InferSelectModel<typeof collections>;
export default collections;

View file

@ -0,0 +1,32 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import games from './games';
export const expansions = pgTable('expansions', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
base_game_id: uuid('base_game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Expansions = InferSelectModel<typeof expansions>;
export const expansion_relations = relations(expansions, ({ one }) => ({
baseGame: one(games, {
fields: [expansions.base_game_id],
references: [games.id],
}),
game: one(games, {
fields: [expansions.game_id],
references: [games.id],
}),
}));

View file

@ -0,0 +1,25 @@
import { pgEnum, pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import type { InferSelectModel } from 'drizzle-orm';
export const ExternalIdType = pgEnum('external_id_type', [
'game',
'category',
'mechanic',
'publisher',
'designer',
'artist',
]);
const externalIds = pgTable('external_ids', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
type: ExternalIdType('type').notNull(),
externalId: text('external_id').notNull(),
});
export type ExternalIds = InferSelectModel<typeof externalIds>;
export default externalIds;

57
src/db/schema/games.ts Normal file
View file

@ -0,0 +1,57 @@
import { index, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { tsvector } from '../../tsVector';
import { type InferSelectModel, relations, sql } from 'drizzle-orm';
import categoriesToGames from './categoriesToGames';
import gamesToExternalIds from './gamesToExternalIds';
import mechanicsToGames from './mechanicsToGames';
import publishersToGames from './publishersToGames';
const games = pgTable(
'games',
{
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
description: text('description'),
year_published: integer('year_published'),
min_players: integer('min_players'),
max_players: integer('max_players'),
playtime: integer('playtime'),
min_playtime: integer('min_playtime'),
max_playtime: integer('max_playtime'),
min_age: integer('min_age'),
image_url: text('image_url'),
thumb_url: text('thumb_url'),
url: text('url'),
text_searchable_index: tsvector('text_searchable_index'),
last_sync_at: timestamp('last_sync_at', {
withTimezone: true,
mode: 'date',
precision: 6,
}),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
},
(table) => {
return {
text_searchable_idx: index('text_searchable_idx')
.on(table.text_searchable_index)
.using(sql`'gin'`),
};
},
);
export const gameRelations = relations(games, ({ many }) => ({
categories_to_games: many(categoriesToGames),
mechanics_to_games: many(mechanicsToGames),
publishers_to_games: many(publishersToGames),
gamesToExternalIds: many(gamesToExternalIds),
}));
export type Games = InferSelectModel<typeof games>;
export default games;

View file

@ -0,0 +1,24 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import games from './games';
import externalIds from './externalIds';
const gamesToExternalIds = pgTable(
'games_to_external_ids',
{
gameId: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
gamesToExternalIdsPkey: primaryKey({
columns: [table.gameId, table.externalId],
}),
};
},
);
export default gamesToExternalIds;

34
src/db/schema/index.ts Normal file
View file

@ -0,0 +1,34 @@
export { default as users, user_relations, type Users } from './users';
export { default as recoveryCodes, type RecoveryCodes } from './recoveryCodes';
export {
default as password_reset_tokens,
password_reset_token_relations,
type PasswordResetTokens,
} from './passwordResetTokens';
export { default as sessions } from './sessions';
export { default as roles, role_relations, type Roles } from './roles';
export { default as userRoles, user_role_relations, type UserRoles } from './userRoles';
export { default as collections, collection_relations, type Collections } from './collections';
export {
default as collectionItems,
collection_item_relations,
type CollectionItems,
} from './collectionItems';
export { default as wishlists, wishlists_relations, type Wishlists } from './wishlists';
export {
default as wishlistItems,
wishlist_item_relations,
type WishlistItems,
} from './wishlistItems';
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 publishers, publishers_relations, type Publishers } from './publishers';
export { default as publishersToGames, publishers_to_games_relations } from './publishersToGames';
export { default as publishersToExternalIds } from './publishersToExternalIds';
export { default as categories, categories_relations, type Categories } from './categories';
export { default as categoriesToExternalIds } from './categoriesToExternalIds';
export { default as categoriesToGames, categories_to_games_relations } from './categoriesToGames';
export { default as mechanics, mechanics_relations, type Mechanics } from './mechanics';
export { default as mechanicsToExternalIds } from './mechanicsToExternalIds';
export { default as mechanicsToGames, mechanics_to_games_relations } from './mechanicsToGames';

View file

@ -0,0 +1,25 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import mechanicsToGames from './mechanicsToGames';
import mechanicsToExternalIds from './mechanicsToExternalIds';
const mechanics = pgTable('mechanics', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Mechanics = InferSelectModel<typeof mechanics>;
export const mechanics_relations = relations(mechanics, ({ many }) => ({
mechanics_to_games: many(mechanicsToGames),
mechanicsToExternalIds: many(mechanicsToExternalIds),
}));
export default mechanics;

View file

@ -0,0 +1,24 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import mechanics from './mechanics';
import externalIds from './externalIds';
const mechanicsToExternalIds = pgTable(
'mechanics_to_external_ids',
{
mechanicId: uuid('mechanic_id')
.notNull()
.references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
mechanicsToExternalIdsPkey: primaryKey({
columns: [table.mechanicId, table.externalId],
}),
};
},
);
export default mechanicsToExternalIds;

View file

@ -0,0 +1,36 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import mechanics from './mechanics';
import games from './games';
const mechanics_to_games = pgTable(
'mechanics_to_games',
{
mechanic_id: uuid('mechanic_id')
.notNull()
.references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
mechanicsToGamesPkey: primaryKey({
columns: [table.mechanic_id, table.game_id],
}),
};
},
);
export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({
mechanic: one(mechanics, {
fields: [mechanics_to_games.mechanic_id],
references: [mechanics.id],
}),
game: one(games, {
fields: [mechanics_to_games.game_id],
references: [games.id],
}),
}));
export default mechanics_to_games;

View file

@ -0,0 +1,30 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
const password_reset_tokens = pgTable('password_reset_tokens', {
id: text('id')
.primaryKey()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expires_at: timestamp('expires_at', {
withTimezone: true,
mode: 'date',
precision: 6,
}),
created_at: timestamp('created_at').notNull().defaultNow(),
});
export type PasswordResetTokens = InferSelectModel<typeof password_reset_tokens>;
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
user: one(users, {
fields: [password_reset_tokens.user_id],
references: [users.id],
}),
}));
export default password_reset_tokens;

View file

@ -0,0 +1,25 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import publishers_to_games from './publishersToGames';
import publishersToExternalIds from './publishersToExternalIds';
const publishers = pgTable('publishers', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Publishers = InferSelectModel<typeof publishers>;
export const publishers_relations = relations(publishers, ({ many }) => ({
publishers_to_games: many(publishers_to_games),
publishersToExternalIds: many(publishersToExternalIds),
}));
export default publishers;

View file

@ -0,0 +1,24 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import publishers from './publishers';
import externalIds from './externalIds';
const publishersToExternalIds = pgTable(
'publishers_to_external_ids',
{
publisherId: uuid('publisher_id')
.notNull()
.references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
publishersToExternalIdsPkey: primaryKey({
columns: [table.publisherId, table.externalId],
}),
};
},
);
export default publishersToExternalIds;

View file

@ -0,0 +1,36 @@
import { pgTable, primaryKey, uuid } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import publishers from './publishers';
import games from './games';
const publishers_to_games = pgTable(
'publishers_to_games',
{
publisher_id: uuid('publisher_id')
.notNull()
.references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
publishersToGamesPkey: primaryKey({
columns: [table.publisher_id, table.game_id],
}),
};
},
);
export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({
publisher: one(publishers, {
fields: [publishers_to_games.publisher_id],
references: [publishers.id],
}),
game: one(games, {
fields: [publishers_to_games.game_id],
references: [games.id],
}),
}));
export default publishers_to_games;

View file

@ -0,0 +1,18 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import type { InferSelectModel } from 'drizzle-orm';
import users from './users';
const recovery_codes = pgTable('recovery_codes', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
code: text('code').notNull(),
used: boolean('used').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;
export default recovery_codes;

21
src/db/schema/roles.ts Normal file
View file

@ -0,0 +1,21 @@
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import user_roles from './userRoles';
const roles = pgTable('roles', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2())
.notNull(),
name: text('name').unique().notNull(),
});
export type Roles = InferSelectModel<typeof roles>;
export const role_relations = relations(roles, ({ many }) => ({
user_roles: many(user_roles),
}));
export default roles;

17
src/db/schema/sessions.ts Normal file
View file

@ -0,0 +1,17 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import users from './users';
const sessions = pgTable('sessions', {
id: text('id').primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
expiresAt: timestamp('expires_at', {
withTimezone: true,
mode: 'date',
}).notNull(),
ipCountry: text('ip_country'),
ipAddress: text('ip_address'),
});
export default sessions;

View file

@ -0,0 +1,36 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
import roles from './roles';
const user_roles = pgTable('user_roles', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
role_id: uuid('role_id')
.notNull()
.references(() => roles.id, { onDelete: 'cascade' }),
primary: boolean('primary').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const user_role_relations = relations(user_roles, ({ one }) => ({
role: one(roles, {
fields: [user_roles.role_id],
references: [roles.id],
}),
user: one(users, {
fields: [user_roles.user_id],
references: [users.id],
}),
}));
export type UserRoles = InferSelectModel<typeof user_roles>;
export default user_roles;

31
src/db/schema/users.ts Normal file
View file

@ -0,0 +1,31 @@
import { boolean, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import user_roles from './userRoles';
const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
username: text('username').unique(),
hashed_password: text('hashed_password'),
email: text('email').unique(),
first_name: text('first_name'),
last_name: text('last_name'),
verified: boolean('verified').default(false),
receive_email: boolean('receive_email').default(false),
theme: text('theme').default('system'),
two_factor_secret: text('two_factor_secret').default(''),
two_factor_enabled: boolean('two_factor_enabled').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const user_relations = relations(users, ({ many }) => ({
user_roles: many(user_roles),
}));
export type Users = InferSelectModel<typeof users>;
export default users;

View file

@ -0,0 +1,35 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import wishlists from './wishlists';
import games from './games';
const wishlist_items = pgTable('wishlist_items', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
wishlist_id: uuid('wishlist_id')
.notNull()
.references(() => wishlists.id, { onDelete: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type WishlistItems = InferSelectModel<typeof wishlist_items>;
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
wishlist: one(wishlists, {
fields: [wishlist_items.wishlist_id],
references: [wishlists.id],
}),
game: one(games, {
fields: [wishlist_items.game_id],
references: [games.id],
}),
}));
export default wishlist_items;

View file

@ -0,0 +1,28 @@
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { type InferSelectModel, relations } from 'drizzle-orm';
import users from './users';
const wishlists = pgTable('wishlists', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Wishlist'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Wishlists = InferSelectModel<typeof wishlists>;
export const wishlists_relations = relations(wishlists, ({ one }) => ({
user: one(users, {
fields: [wishlists.user_id],
references: [users.id],
}),
}));
export default wishlists;

View file

@ -2,7 +2,7 @@ import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres'; import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg'; import pg from 'pg';
import * as schema from './schema'; import * as schema from './schema';
import {Argon2id} from "oslo/password"; import { Argon2id } from 'oslo/password';
// create the connection // create the connection
const pool = new pg.Pool({ const pool = new pg.Pool({
@ -11,7 +11,7 @@ const pool = new pg.Pool({
host: process.env.DATABASE_HOST, host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT).valueOf(), port: Number(process.env.DATABASE_PORT).valueOf(),
database: process.env.DATABASE_DB, database: process.env.DATABASE_DB,
ssl: process.env.DATABASE_HOST !== 'localhost' ssl: process.env.DATABASE_HOST !== 'localhost',
}); });
const db = drizzle(pool, { schema: schema }); const db = drizzle(pool, { schema: schema });
@ -40,31 +40,37 @@ console.log('Roles created.');
console.log('Admin Role: ', adminRole); console.log('Admin Role: ', adminRole);
const adminUser = await db const adminUser = await db
.insert(schema.users) .insert(schema.users)
.values({ .values({
username: `${process.env.ADMIN_USERNAME}`, username: `${process.env.ADMIN_USERNAME}`,
email: '', email: '',
hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`), hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`),
first_name: 'Brad', first_name: 'Brad',
last_name: 'S', last_name: 'S',
verified: true verified: true,
}) })
.returning() .returning()
.onConflictDoNothing(); .onConflictDoNothing();
console.log('Admin user created.', adminUser); console.log('Admin user created.', adminUser);
await db.insert(schema.user_roles).values({ await db
user_id: adminUser[0].id, .insert(schema.user_roles)
role_id: adminRole[0].id .values({
}).onConflictDoNothing(); user_id: adminUser[0].id,
role_id: adminRole[0].id,
})
.onConflictDoNothing();
console.log('Admin user given admin role.'); console.log('Admin user given admin role.');
await db.insert(schema.user_roles).values({ await db
user_id: adminUser[0].id, .insert(schema.user_roles)
role_id: userRole[0].id .values({
}).onConflictDoNothing(); user_id: adminUser[0].id,
role_id: userRole[0].id,
})
.onConflictDoNothing();
console.log('Admin user given user role.'); console.log('Admin user given user role.');

44
src/env.ts Normal file
View file

@ -0,0 +1,44 @@
import { ZodError, z } from 'zod';
const stringBoolean = z.coerce
.string()
.transform((val) => {
return val === 'true';
})
.default('false');
const EnvSchema = z.object({
NODE_ENV: z.string().default('development'),
DATABASE_USER: z.string(),
DATABASE_PASSWORD: z.string(),
DATABASE_HOST: z.string(),
DATABASE_PORT: z.coerce.number(),
DATABASE_DB: z.string(),
DATABASE_URL: z.string(),
PUBLIC_SITE_URL: z.string(),
PUBLIC_UMAMI_DO_NOT_TRACK: z.string(),
PUBLIC_UMAMI_ID: z.string(),
PUBLIC_UMAMI_URL: z.string(),
DB_MIGRATING: stringBoolean,
DB_SEEDING: stringBoolean,
});
export type EnvSchema = z.infer<typeof EnvSchema>;
try {
EnvSchema.parse(process.env);
} catch (error) {
if (error instanceof ZodError) {
let message = 'Missing required values in .env:\n';
error.issues.forEach((issue) => {
message += issue.path[0] + '\n';
});
const e = new Error(message);
e.stack = '';
throw e;
} else {
console.error(error);
}
}
export default EnvSchema.parse(process.env);

View file

@ -2,7 +2,7 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import { MinusCircle, PlusCircle } from "lucide-svelte"; import { MinusCircle, PlusCircle } from "lucide-svelte";
import { Button } from '$components/ui/button'; import { Button } from '$components/ui/button';
import type { CollectionItems, Wishlists } from "../../schema"; import type { CollectionItems, Wishlists } from "$db/schema";
export let game_id: string; export let game_id: string;
export let collection: CollectionItems; export let collection: CollectionItems;

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { GameType, SavedGameType } from '$lib/types'; import type { GameType, SavedGameType } from '$lib/types';
import * as Card from "$lib/components/ui/card"; import * as Card from "$lib/components/ui/card";
import type { CollectionItems } from '../../schema'; import type { CollectionItems } from '$db/schema';
export let game: GameType | CollectionItems; export let game: GameType | CollectionItems;
export let variant: 'default' | 'compact' = 'default'; export let variant: 'default' | 'compact' = 'default';

View file

@ -6,7 +6,7 @@
import * as Avatar from '$components/ui/avatar'; import * as Avatar from '$components/ui/avatar';
import { invalidateAll } from '$app/navigation'; import { invalidateAll } from '$app/navigation';
import Logo from '$components/logo.svelte'; import Logo from '$components/logo.svelte';
import type { Users } from '../../schema'; import type { Users } from '$db/schema';
export let user: Users | null = null; export let user: Users | null = null;

View file

@ -1,8 +1,8 @@
import { generateIdFromEntropySize } from 'lucia'; import { generateIdFromEntropySize } from 'lucia';
import { TimeSpan, createDate } from 'oslo'; import { TimeSpan, createDate } from 'oslo';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle'; import db from '../../db';
import { password_reset_tokens } from '../../schema'; import { password_reset_tokens } from '$db/schema';
export async function createPasswordResetToken(userId: string): Promise<string> { export async function createPasswordResetToken(userId: string): Promise<string> {
// optionally invalidate all existing tokens // optionally invalidate all existing tokens

View file

@ -1,8 +1,8 @@
// lib/server/lucia.ts // lib/server/lucia.ts
import { Lucia, TimeSpan } from 'lucia'; import { Lucia, TimeSpan } from 'lucia';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import db from '$lib/drizzle'; import db from '../../db';
import { sessions, users } from '../../schema'; import { sessions, users } from '$db/schema';
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);
@ -19,7 +19,7 @@ export const lucia = new Lucia(adapter, {
getSessionAttributes: (attributes) => { getSessionAttributes: (attributes) => {
return { return {
ipCountry: attributes.ip_country, ipCountry: attributes.ip_country,
ipAddress: attributes.ip_address ipAddress: attributes.ip_address,
}; };
}, },
getUserAttributes: (attributes) => { getUserAttributes: (attributes) => {
@ -28,7 +28,7 @@ export const lucia = new Lucia(adapter, {
email: attributes.email, email: attributes.email,
firstName: attributes.firstName, firstName: attributes.firstName,
lastName: attributes.lastName, lastName: attributes.lastName,
theme: attributes.theme theme: attributes.theme,
}; };
}, },
sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days
@ -39,9 +39,9 @@ export const lucia = new Lucia(adapter, {
// set to `true` when using HTTPS // set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production', secure: process.env.NODE_ENV === 'production',
sameSite: 'strict', sameSite: 'strict',
domain domain,
} },
} },
}); });
declare module 'lucia' { declare module 'lucia' {

View file

@ -1,5 +1,5 @@
import type { SvelteComponent } from 'svelte'; import type { SvelteComponent } from 'svelte';
import { collections } from '../schema'; import { collections } from '$db/schema';
export type Message = { status: 'error' | 'success' | 'warning' | 'info'; text: string }; export type Message = { status: 'error' | 'success' | 'warning' | 'info'; text: string };

View file

@ -2,8 +2,14 @@ import { error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import kebabCase from 'just-kebab-case'; import kebabCase from 'just-kebab-case';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
import db from '$lib/drizzle'; import db from '../../../db';
import { externalIds, type Mechanics, type Categories, categories, categoriesToExternalIds } from '../../../schema'; import {
externalIds,
type Mechanics,
type Categories,
categories,
categoriesToExternalIds,
} from '$db/schema';
export async function createCategory(locals: App.Locals, category: Categories, externalId: string) { export async function createCategory(locals: App.Locals, category: Categories, externalId: string) {
if (!category || !externalId || externalId === '') { if (!category || !externalId || externalId === '') {
@ -12,7 +18,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
try { try {
const dbExternalId = await db.query.externalIds.findFirst({ const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId) where: eq(externalIds.externalId, externalId),
}); });
if (dbExternalId) { if (dbExternalId) {
@ -20,7 +26,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
.select({ .select({
id: categories.id, id: categories.id,
name: categories.name, name: categories.name,
slug: categories.slug slug: categories.slug,
}) })
.from(categories) .from(categories)
.leftJoin(categoriesToExternalIds, eq(categoriesToExternalIds.externalId, externalId)); .leftJoin(categoriesToExternalIds, eq(categoriesToExternalIds.externalId, externalId));
@ -30,9 +36,9 @@ export async function createCategory(locals: App.Locals, category: Categories, e
return new Response('Mechanic already exists', { return new Response('Mechanic already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundCategory[0].id}` Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundCategory[0].id}`,
}, },
status: 409 status: 409,
}); });
} }
} }
@ -44,25 +50,25 @@ export async function createCategory(locals: App.Locals, category: Categories, e
.insert(categories) .insert(categories)
.values({ .values({
name: category.name, name: category.name,
slug: kebabCase(category.name ?? category.slug ?? '') slug: kebabCase(category.name ?? category.slug ?? ''),
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'category' type: 'category',
}) })
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(categoriesToExternalIds).values({ await transaction.insert(categoriesToExternalIds).values({
categoryId: dbCategory[0].id, categoryId: dbCategory[0].id,
externalId: dbExternalIds[0].id externalId: dbExternalIds[0].id,
}); });
}); });
if (dbCategory.length === 0) { if (dbCategory.length === 0) {
return new Response('Could not create category', { return new Response('Could not create category', {
status: 500 status: 500,
}); });
} }

View file

@ -1,7 +1,7 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm'; import { and, eq } from 'drizzle-orm';
import db from '$lib/drizzle'; import db from '../../../db';
import { type Expansions, expansions } from '../../../schema'; import { type Expansions, expansions } from '$db/schema';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
export async function createExpansion(locals: App.Locals, expansion: Expansions) { export async function createExpansion(locals: App.Locals, expansion: Expansions) {
@ -10,24 +10,26 @@ export async function createExpansion(locals: App.Locals, expansion: Expansions)
} }
try { try {
const foundExpansion = await db.query.expansions const foundExpansion = await db.query.expansions.findFirst({
.findFirst({ where: and(
where: and(eq(expansions.base_game_id, expansion.base_game_id), eq(expansions.game_id, expansion.game_id)), eq(expansions.base_game_id, expansion.base_game_id),
columns: { eq(expansions.game_id, expansion.game_id),
id: true, ),
game_id: true, columns: {
base_game_id: true id: true,
} game_id: true,
}); base_game_id: true,
},
});
console.log('Expansion already exists', foundExpansion); console.log('Expansion already exists', foundExpansion);
if (foundExpansion) { if (foundExpansion) {
console.log('Expansion Game ID', foundExpansion.game_id); console.log('Expansion Game ID', foundExpansion.game_id);
return new Response('Expansion already exists', { return new Response('Expansion already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/game/${foundExpansion.game_id}` Location: `${PUBLIC_SITE_URL}/api/game/${foundExpansion.game_id}`,
}, },
status: 409 status: 409,
}); });
} }
@ -42,7 +44,7 @@ export async function createExpansion(locals: App.Locals, expansion: Expansions)
if (dbExpansion.length === 0) { if (dbExpansion.length === 0) {
return new Response('Could not create expansion', { return new Response('Could not create expansion', {
status: 500 status: 500,
}); });
} }
@ -175,4 +177,4 @@ export async function createExpansion(locals: App.Locals, expansion: Expansions)
// console.error(e); // console.error(e);
// throw new Error('Something went wrong creating Expansion'); // throw new Error('Something went wrong creating Expansion');
// } // }
// } // }

View file

@ -1,6 +1,6 @@
import kebabCase from 'just-kebab-case'; import kebabCase from 'just-kebab-case';
import db from '$lib/drizzle'; import db from '../../../db';
import { externalIds, gamesToExternalIds, type Games, games } from '../../../schema'; import { externalIds, gamesToExternalIds, type Games, games } from '$db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
@ -12,12 +12,12 @@ export async function getGame(locals: App.Locals, id: string) {
try { try {
return await db.query.games.findFirst({ return await db.query.games.findFirst({
where: eq(games.id, id) where: eq(games.id, id),
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return new Response('Could not get games', { return new Response('Could not get games', {
status: 500 status: 500,
}); });
} }
} }
@ -29,7 +29,7 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st
try { try {
const dbExternalId = await db.query.externalIds.findFirst({ const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId) where: eq(externalIds.externalId, externalId),
}); });
if (dbExternalId) { if (dbExternalId) {
@ -37,7 +37,7 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st
.select({ .select({
id: games.id, id: games.id,
name: games.name, name: games.name,
slug: games.slug slug: games.slug,
}) })
.from(games) .from(games)
.leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)); .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId));
@ -47,9 +47,9 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st
return new Response('Game already exists', { return new Response('Game already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}` Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`,
}, },
status: 409 status: 409,
}); });
} }
} }
@ -71,25 +71,25 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'game' type: 'game',
}) })
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(gamesToExternalIds).values({ await transaction.insert(gamesToExternalIds).values({
gameId: dbGames[0].id, gameId: dbGames[0].id,
externalId: dbExternalIds[0].id externalId: dbExternalIds[0].id,
}); });
}); });
if (dbGames.length === 0) { if (dbGames.length === 0) {
return new Response('Could not create game', { return new Response('Could not create game', {
status: 500 status: 500,
}); });
} }
@ -103,7 +103,11 @@ export async function createGame(locals: App.Locals, game: Games, externalId: st
} }
} }
export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games, externalId: string) { export async function createOrUpdateGameMinimal(
locals: App.Locals,
game: Games,
externalId: string,
) {
if (!game || !externalId || externalId === '') { if (!game || !externalId || externalId === '') {
error(400, 'Invalid Request'); error(400, 'Invalid Request');
} }
@ -128,7 +132,7 @@ export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games,
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
}) })
.onConflictDoUpdate({ .onConflictDoUpdate({
target: games.id, target: games.id,
@ -144,27 +148,30 @@ export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games,
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
} },
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'game' type: 'game',
}) })
.onConflictDoNothing() .onConflictDoNothing()
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(gamesToExternalIds).values({ await transaction
gameId: dbGames[0].id, .insert(gamesToExternalIds)
externalId: dbExternalIds[0].id .values({
}).onConflictDoNothing(); gameId: dbGames[0].id,
externalId: dbExternalIds[0].id,
})
.onConflictDoNothing();
}); });
if (dbGames.length === 0) { if (dbGames.length === 0) {
return new Response('Could not create game', { return new Response('Could not create game', {
status: 500 status: 500,
}); });
} }
@ -186,7 +193,7 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern
try { try {
const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`; const externalUrl = `https://boardgamegeek.com/boardgame/${externalId}`;
const dbExternalId = await db.query.externalIds.findFirst({ const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId) where: eq(externalIds.externalId, externalId),
}); });
if (dbExternalId) { if (dbExternalId) {
@ -194,7 +201,7 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern
.select({ .select({
id: games.id, id: games.id,
name: games.name, name: games.name,
slug: games.slug slug: games.slug,
}) })
.from(games) .from(games)
.leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId)); .leftJoin(gamesToExternalIds, eq(gamesToExternalIds.externalId, externalId));
@ -204,9 +211,9 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern
return new Response('Game already exists', { return new Response('Game already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}` Location: `${PUBLIC_SITE_URL}/api/game/${foundGame[0].id}`,
}, },
status: 409 status: 409,
}); });
} }
} }
@ -228,7 +235,7 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
}) })
.onConflictDoUpdate({ .onConflictDoUpdate({
target: games.id, target: games.id,
@ -244,27 +251,30 @@ export async function createOrUpdateGame(locals: App.Locals, game: Games, extern
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
} },
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'game' type: 'game',
}) })
.onConflictDoNothing() .onConflictDoNothing()
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(gamesToExternalIds).values({ await transaction
gameId: dbGames[0].id, .insert(gamesToExternalIds)
externalId: dbExternalIds[0].id .values({
}).onConflictDoNothing(); gameId: dbGames[0].id,
externalId: dbExternalIds[0].id,
})
.onConflictDoNothing();
}); });
if (dbGames.length === 0) { if (dbGames.length === 0) {
return new Response('Could not create game', { return new Response('Could not create game', {
status: 500 status: 500,
}); });
} }
@ -298,142 +308,142 @@ export async function updateGame(locals: App.Locals, game: Games, id: string) {
min_players: game.min_players, min_players: game.min_players,
max_players: game.max_players, max_players: game.max_players,
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime max_playtime: game.max_playtime,
}) })
.where(eq(games.id, id)) .where(eq(games.id, id))
.returning(); .returning();
return new Response(JSON.stringify(dbGame[0]), { return new Response(JSON.stringify(dbGame[0]), {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return new Response('Could not get publishers', { return new Response('Could not get publishers', {
status: 500 status: 500,
}); });
} }
} }
// console.log('Creating or updating game', JSON.stringify(game, null, 2)); // console.log('Creating or updating game', JSON.stringify(game, null, 2));
// const categoryIds = game.categories; // const categoryIds = game.categories;
// const mechanicIds = game.mechanics; // const mechanicIds = game.mechanics;
// const publisherIds = game.publishers; // const publisherIds = game.publishers;
// const designerIds = game.designers; // const designerIds = game.designers;
// const artistIds = game.artists; // const artistIds = game.artists;
// // const expansionIds = game.expansions; // // const expansionIds = game.expansions;
// const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`; // const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
// console.log('categoryIds', categoryIds); // console.log('categoryIds', categoryIds);
// console.log('mechanicIds', mechanicIds); // console.log('mechanicIds', mechanicIds);
// await db.transaction(async (transaction) => { // await db.transaction(async (transaction) => {
// const dbGame = await db.transaction(async (transaction) => { // const dbGame = await db.transaction(async (transaction) => {
// transaction.insert(games).values({ // transaction.insert(games).values({
// name: game.name, // name: game.name,
// slug: kebabCase(game.name || ''), // slug: kebabCase(game.name || ''),
// description: game.description, // description: game.description,
// external_id: game.external_id, // external_id: game.external_id,
// url: externalUrl, // url: externalUrl,
// thumb_url: game.thumb_url, // thumb_url: game.thumb_url,
// image_url: game.image_url, // image_url: game.image_url,
// min_age: game.min_age || 0, // min_age: game.min_age || 0,
// min_players: game.min_players || 0, // min_players: game.min_players || 0,
// max_players: game.max_players || 0, // max_players: game.max_players || 0,
// min_playtime: game.min_playtime || 0, // min_playtime: game.min_playtime || 0,
// max_playtime: game.max_playtime || 0, // max_playtime: game.max_playtime || 0,
// year_published: game.year_published || 0, // year_published: game.year_published || 0,
// last_sync_at: new Date(), // last_sync_at: new Date(),
// }).onConflictDoUpdate({ // }).onConflictDoUpdate({
// target: games.id, set: { // target: games.id, set: {
// name: game.name, // name: game.name,
// slug: kebabCase(game.name), // slug: kebabCase(game.name),
// description: game.description, // description: game.description,
// external_id: game.external_id, // external_id: game.external_id,
// url: externalUrl, // url: externalUrl,
// thumb_url: game.thumb_url, // thumb_url: game.thumb_url,
// image_url: game.image_url, // image_url: game.image_url,
// min_age: game.min_age || 0, // min_age: game.min_age || 0,
// min_players: game.min_players || 0, // min_players: game.min_players || 0,
// max_players: game.max_players || 0, // max_players: game.max_players || 0,
// min_playtime: game.min_playtime || 0, // min_playtime: game.min_playtime || 0,
// max_playtime: game.max_playtime || 0, // max_playtime: game.max_playtime || 0,
// year_published: game.year_published || 0, // year_published: game.year_published || 0,
// last_sync_at: new Date(), // last_sync_at: new Date(),
// } // }
// }).returning(); // }).returning();
// }); // });
// // TODO: Connect to everything else // // TODO: Connect to everything else
// }); // });
// await db.insert(games).values({ // await db.insert(games).values({
// include: { // include: {
// mechanics: true, // mechanics: true,
// publishers: true, // publishers: true,
// designers: true, // designers: true,
// artists: true, // artists: true,
// expansions: true // expansions: true
// }, // },
// where: { // where: {
// external_id: game.external_id // external_id: game.external_id
// }, // },
// create: { // create: {
// name: game.name, // name: game.name,
// slug: kebabCase(game.name), // slug: kebabCase(game.name),
// description: game.description, // description: game.description,
// external_id: game.external_id, // external_id: game.external_id,
// url: externalUrl, // url: externalUrl,
// thumb_url: game.thumb_url, // thumb_url: game.thumb_url,
// image_url: game.image_url, // image_url: game.image_url,
// min_age: game.min_age || 0, // min_age: game.min_age || 0,
// min_players: game.min_players || 0, // min_players: game.min_players || 0,
// max_players: game.max_players || 0, // max_players: game.max_players || 0,
// min_playtime: game.min_playtime || 0, // min_playtime: game.min_playtime || 0,
// max_playtime: game.max_playtime || 0, // max_playtime: game.max_playtime || 0,
// year_published: game.year_published || 0, // year_published: game.year_published || 0,
// last_sync_at: new Date(), // last_sync_at: new Date(),
// categories: { // categories: {
// connect: categoryIds // connect: categoryIds
// }, // },
// mechanics: { // mechanics: {
// connect: mechanicIds // connect: mechanicIds
// }, // },
// publishers: { // publishers: {
// connect: publisherIds // connect: publisherIds
// }, // },
// designers: { // designers: {
// connect: designerIds // connect: designerIds
// }, // },
// artists: { // artists: {
// connect: artistIds // connect: artistIds
// } // }
// }, // },
// update: { // update: {
// name: game.name, // name: game.name,
// slug: kebabCase(game.name), // slug: kebabCase(game.name),
// description: game.description, // description: game.description,
// external_id: game.external_id, // external_id: game.external_id,
// url: externalUrl, // url: externalUrl,
// thumb_url: game.thumb_url, // thumb_url: game.thumb_url,
// image_url: game.image_url, // image_url: game.image_url,
// min_age: game.min_age || 0, // min_age: game.min_age || 0,
// min_players: game.min_players || 0, // min_players: game.min_players || 0,
// max_players: game.max_players || 0, // max_players: game.max_players || 0,
// min_playtime: game.min_playtime || 0, // min_playtime: game.min_playtime || 0,
// max_playtime: game.max_playtime || 0, // max_playtime: game.max_playtime || 0,
// year_published: game.year_published || 0, // year_published: game.year_published || 0,
// last_sync_at: new Date(), // last_sync_at: new Date(),
// categories: { // categories: {
// connect: categoryIds // connect: categoryIds
// }, // },
// mechanics: { // mechanics: {
// connect: mechanicIds // connect: mechanicIds
// }, // },
// publishers: { // publishers: {
// connect: publisherIds // connect: publisherIds
// }, // },
// designers: { // designers: {
// connect: designerIds // connect: designerIds
// }, // },
// artists: { // artists: {
// connect: artistIds // connect: artistIds
// } // }
// } // }
// }); // });

View file

@ -1,6 +1,6 @@
import kebabCase from 'just-kebab-case'; import kebabCase from 'just-kebab-case';
import db from '$lib/drizzle'; import db from '../../../db';
import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '../../../schema'; import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '$db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
@ -12,7 +12,7 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
try { try {
const dbExternalId = await db.query.externalIds.findFirst({ const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId) where: eq(externalIds.externalId, externalId),
}); });
if (dbExternalId) { if (dbExternalId) {
@ -20,7 +20,7 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
.select({ .select({
id: mechanics.id, id: mechanics.id,
name: mechanics.name, name: mechanics.name,
slug: mechanics.slug slug: mechanics.slug,
}) })
.from(mechanics) .from(mechanics)
.leftJoin(mechanicsToExternalIds, eq(mechanicsToExternalIds.externalId, externalId)); .leftJoin(mechanicsToExternalIds, eq(mechanicsToExternalIds.externalId, externalId));
@ -30,9 +30,9 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
return new Response('Mechanic already exists', { return new Response('Mechanic already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundMechanic[0].id}` Location: `${PUBLIC_SITE_URL}/api/mechanic/${foundMechanic[0].id}`,
}, },
status: 409 status: 409,
}); });
} }
} }
@ -44,25 +44,25 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
.insert(mechanics) .insert(mechanics)
.values({ .values({
name: mechanic.name, name: mechanic.name,
slug: kebabCase(mechanic.name || mechanic.slug || '') slug: kebabCase(mechanic.name || mechanic.slug || ''),
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'mechanic' type: 'mechanic',
}) })
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(mechanicsToExternalIds).values({ await transaction.insert(mechanicsToExternalIds).values({
mechanicId: dbMechanics[0].id, mechanicId: dbMechanics[0].id,
externalId: dbExternalIds[0].id externalId: dbExternalIds[0].id,
}); });
}); });
if (dbMechanics.length === 0) { if (dbMechanics.length === 0) {
return new Response('Could not create mechanic', { return new Response('Could not create mechanic', {
status: 500 status: 500,
}); });
} }

View file

@ -1,13 +1,8 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import kebabCase from 'just-kebab-case'; import kebabCase from 'just-kebab-case';
import db from '$lib/drizzle'; import db from '../../../db';
import { import { externalIds, publishersToExternalIds, type Publishers, publishers } from '$db/schema';
externalIds,
publishersToExternalIds,
type Publishers,
publishers,
} from '../../../schema';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
export async function getPublisher(locals: App.Locals, id: string) { export async function getPublisher(locals: App.Locals, id: string) {
@ -17,8 +12,8 @@ export async function getPublisher(locals: App.Locals, id: string) {
} }
return new Response(JSON.stringify(publisher[0]), { return new Response(JSON.stringify(publisher[0]), {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}); });
} }
@ -32,19 +27,19 @@ export async function updatePublisher(locals: App.Locals, publisher: Publishers,
.update(publishers) .update(publishers)
.set({ .set({
name: publisher.name, name: publisher.name,
slug: kebabCase(publisher.name || '') slug: kebabCase(publisher.name || ''),
}) })
.where(eq(publishers.id, id)) .where(eq(publishers.id, id))
.returning(); .returning();
return new Response(JSON.stringify(dbPublisher[0]), { return new Response(JSON.stringify(dbPublisher[0]), {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return new Response('Could not get publishers', { return new Response('Could not get publishers', {
status: 500 status: 500,
}); });
} }
} }
@ -52,7 +47,7 @@ export async function updatePublisher(locals: App.Locals, publisher: Publishers,
export async function createPublisher( export async function createPublisher(
locals: App.Locals, locals: App.Locals,
publisher: Publishers, publisher: Publishers,
externalId: string externalId: string,
) { ) {
if (!publisher || !externalId || externalId === '') { if (!publisher || !externalId || externalId === '') {
error(400, 'Invalid Request'); error(400, 'Invalid Request');
@ -60,7 +55,7 @@ export async function createPublisher(
try { try {
const dbExternalId = await db.query.externalIds.findFirst({ const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId) where: eq(externalIds.externalId, externalId),
}); });
if (dbExternalId) { if (dbExternalId) {
@ -68,7 +63,7 @@ export async function createPublisher(
.select({ .select({
id: publishers.id, id: publishers.id,
name: publishers.name, name: publishers.name,
slug: publishers.slug slug: publishers.slug,
}) })
.from(publishers) .from(publishers)
.leftJoin(publishersToExternalIds, eq(publishersToExternalIds.externalId, externalId)); .leftJoin(publishersToExternalIds, eq(publishersToExternalIds.externalId, externalId));
@ -78,9 +73,9 @@ export async function createPublisher(
return new Response('Publisher already exists', { return new Response('Publisher already exists', {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Location: `${PUBLIC_SITE_URL}/api/publisher/${foundPublisher[0].id}` Location: `${PUBLIC_SITE_URL}/api/publisher/${foundPublisher[0].id}`,
}, },
status: 409 status: 409,
}); });
} }
} }
@ -92,25 +87,25 @@ export async function createPublisher(
.insert(publishers) .insert(publishers)
.values({ .values({
name: publisher.name, name: publisher.name,
slug: kebabCase(publisher.name || publisher.slug || '') slug: kebabCase(publisher.name || publisher.slug || ''),
}) })
.returning(); .returning();
const dbExternalIds = await transaction const dbExternalIds = await transaction
.insert(externalIds) .insert(externalIds)
.values({ .values({
externalId, externalId,
type: 'publisher' type: 'publisher',
}) })
.returning({ id: externalIds.id }); .returning({ id: externalIds.id });
await transaction.insert(publishersToExternalIds).values({ await transaction.insert(publishersToExternalIds).values({
publisherId: dbPublishers[0].id, publisherId: dbPublishers[0].id,
externalId: dbExternalIds[0].id externalId: dbExternalIds[0].id,
}); });
}); });
if (dbPublishers.length === 0) { if (dbPublishers.length === 0) {
return new Response('Could not create publisher', { return new Response('Could not create publisher', {
status: 500 status: 500,
}); });
} }
@ -122,4 +117,4 @@ export async function createPublisher(
console.error(e); console.error(e);
throw new Error('Something went wrong creating Publisher'); throw new Error('Something went wrong creating Publisher');
} }
} }

View file

@ -1,6 +1,6 @@
import type { GameType, SavedGameType } from '$lib/types'; import type { GameType, SavedGameType } from '$lib/types';
import kebabCase from 'just-kebab-case'; import kebabCase from 'just-kebab-case';
import type { Games } from '../../schema'; import type { Games } from '$db/schema';
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType { export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
return { return {
@ -9,7 +9,7 @@ export function convertToSavedGame(game: GameType | SavedGameType): SavedGameTyp
thumb_url: game.thumb_url, thumb_url: game.thumb_url,
players: game.players, players: game.players,
playtime: game.playtime, playtime: game.playtime,
searchTerms: `${game.name.toLowerCase()}` searchTerms: `${game.name.toLowerCase()}`,
}; };
} }
@ -39,7 +39,7 @@ export function mapSavedGameToGame(game: SavedGameType): GameType {
description: '', description: '',
description_preview: '', description_preview: '',
players, players,
playtime playtime,
}; };
} }
@ -56,6 +56,6 @@ export function mapAPIGameToBoredGame(game: GameType): Games {
min_playtime: game.min_playtime, min_playtime: game.min_playtime,
max_playtime: game.max_playtime, max_playtime: game.max_playtime,
min_age: game.min_age, min_age: game.min_age,
description: game.description description: game.description,
}; };
} }

View file

@ -1,8 +1,8 @@
import { redirect, loadFlash } from 'sveltekit-flash-message/server'; import { redirect, loadFlash } from 'sveltekit-flash-message/server';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle'; import db from '../../../../db';
import { user_roles } from '../../../../schema'; import { user_roles } from '$db/schema';
export const load = loadFlash(async (event) => { export const load = loadFlash(async (event) => {
const { locals } = event; const { locals } = event;
@ -16,10 +16,10 @@ export const load = loadFlash(async (event) => {
with: { with: {
role: { role: {
columns: { columns: {
name: true name: true,
} },
} },
} },
}); });
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');

View file

@ -1,23 +1,20 @@
import { redirect } from "sveltekit-flash-message/server"; import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from "./$types"; import type { PageServerLoad } from './$types';
import { notSignedInMessage } from "$lib/flashMessages"; import { notSignedInMessage } from '$lib/flashMessages';
import db from "$lib/drizzle"; import db from '../../../../../db';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
// TODO: Ensure admin user // TODO: Ensure admin user
if (!event.locals.user) { if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event); redirect(302, '/login', notSignedInMessage, event);
} }
const users = await db.query const users = await db.query.users.findMany({
.users limit: 10,
.findMany({ offset: 0,
limit: 10, });
offset: 0
});
return { return {
users users,
}; };
}; };

View file

@ -2,8 +2,8 @@ import { and, eq, inArray, not } from 'drizzle-orm';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'; import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle'; import db from '../../../../../../db';
import { roles, user_roles, users } from '../../../../../../schema'; import { roles, user_roles, users } from '$db/schema';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const { params } = event; const { params } = event;
@ -22,16 +22,16 @@ export const load: PageServerLoad = async (event) => {
role: { role: {
columns: { columns: {
name: true, name: true,
cuid: true cuid: true,
} },
} },
} },
} },
} },
}); });
const containsAdminRole = foundUser?.user_roles?.some( const containsAdminRole = foundUser?.user_roles?.some(
(user_role) => user_role?.role?.name === 'admin' (user_role) => user_role?.role?.name === 'admin',
); );
if (!containsAdminRole) { if (!containsAdminRole) {
console.log('Not an admin'); console.log('Not an admin');
@ -45,14 +45,14 @@ export const load: PageServerLoad = async (event) => {
where: not(inArray(roles.cuid, currentRoleIds)), where: not(inArray(roles.cuid, currentRoleIds)),
columns: { columns: {
name: true, name: true,
cuid: true cuid: true,
} },
}); });
} }
return { return {
user: foundUser, user: foundUser,
availableRoles availableRoles,
}; };
}; };
@ -71,10 +71,10 @@ export const actions = {
role: { role: {
columns: { columns: {
name: true, name: true,
cuid: true cuid: true,
} },
} },
} },
}); });
console.log('userRoles', userRoles); console.log('userRoles', userRoles);
@ -88,13 +88,13 @@ export const actions = {
const data = await request.formData(); const data = await request.formData();
const role = data.get('role'); const role = data.get('role');
const dbRole = await db.query.roles.findFirst({ const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '') where: eq(roles.cuid, role?.toString() ?? ''),
}); });
console.log('dbRole', dbRole); console.log('dbRole', dbRole);
if (dbRole) { if (dbRole) {
await db.insert(user_roles).values({ await db.insert(user_roles).values({
user_id: user.id, user_id: user.id,
role_id: dbRole.id role_id: dbRole.id,
}); });
redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event); redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event);
} else { } else {
@ -114,10 +114,10 @@ export const actions = {
role: { role: {
columns: { columns: {
name: true, name: true,
cuid: true cuid: true,
} },
} },
} },
}); });
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin'); const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
@ -128,7 +128,7 @@ export const actions = {
const data = await request.formData(); const data = await request.formData();
const role = data.get('role'); const role = data.get('role');
const dbRole = await db.query.roles.findFirst({ const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '') where: eq(roles.cuid, role?.toString() ?? ''),
}); });
console.log('dbRole', dbRole); console.log('dbRole', dbRole);
if (dbRole) { if (dbRole) {
@ -139,5 +139,5 @@ export const actions = {
} else { } else {
redirect({ type: 'error', message: `Failed to remove role ${dbRole.name} !` }, event); redirect({ type: 'error', message: `Failed to remove role ${dbRole.name} !` }, event);
} }
} },
}; };

View file

@ -21,7 +21,7 @@
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import DataTableCheckbox from "./user-table-checkbox.svelte"; import DataTableCheckbox from "./user-table-checkbox.svelte";
import type { Users } from '../../../../../schema'; import type { Users } from '$db/schema';
export let users: Users[] = []; export let users: Users[] = [];

View file

@ -5,8 +5,8 @@ import { zod } from 'sveltekit-superforms/adapters';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { modifyListGameSchema, type ListGame } from '$lib/validations/zod-schemas'; import { modifyListGameSchema, type ListGame } from '$lib/validations/zod-schemas';
import { search_schema } from '$lib/zodValidation.js'; import { search_schema } from '$lib/zodValidation.js';
import db from '$lib/drizzle'; import db from '../../../../db';
import { collection_items, collections, games } from '../../../../schema'; import { collection_items, collections, games } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
export async function load(event) { export async function load(event) {

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { type ListGame, modifyListGameSchema } from '$lib/validations/zod-schemas'; import { type ListGame, modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '$lib/drizzle.js'; import db from '../../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js'; import { notSignedInMessage } from '$lib/flashMessages.js';
import { collections, games, collection_items } from '../../../../../schema.js'; import { collections, games, collection_items } from '$db/schema';
import { search_schema } from '$lib/zodValidation'; import { search_schema } from '$lib/zodValidation';
import type { UICollection } from '$lib/types'; import type { UICollection } from '$lib/types';

View file

@ -1,7 +1,7 @@
import { fail } from '@sveltejs/kit'; import { fail } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js'; import db from '../../../../db';
import { wishlists } from '../../../../schema.js'; import { wishlists } from '$db/schema';
export async function load({ locals }) { export async function load({ locals }) {
if (!locals.user) { if (!locals.user) {
@ -10,16 +10,16 @@ export async function load({ locals }) {
try { try {
const userWishlists = await db.query.wishlists.findMany({ const userWishlists = await db.query.wishlists.findMany({
where: eq(wishlists.user_id, locals.user.id) where: eq(wishlists.user_id, locals.user.id),
}); });
return { return {
wishlsits: userWishlists wishlsits: userWishlists,
}; };
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
return { return {
wishlists: [] wishlists: [],
}; };
} }

View file

@ -1,10 +1,10 @@
import { type Actions, fail, redirect } from "@sveltejs/kit"; import { type Actions, fail, redirect } from '@sveltejs/kit';
import { eq } from "drizzle-orm"; import { eq } from 'drizzle-orm';
import { zod } from "sveltekit-superforms/adapters"; import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import db from "$lib/drizzle.js"; import db from '../../../../../db';
import { modifyListGameSchema } from "$lib/validations/zod-schemas"; import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import { games, wishlist_items, wishlists } from "../../../../../schema.js"; import { games, wishlist_items, wishlists } from '$db/schema';
export async function load({ params, locals }) { export async function load({ params, locals }) {
const user = locals.user; const user = locals.user;
@ -13,20 +13,22 @@ export async function load({ params, locals }) {
} }
try { try {
const wishlist = await db.select({ const wishlist = await db
wishlistId: wishlists.id, .select({
wishlistItems: { wishlistId: wishlists.id,
id: wishlist_items.id, wishlistItems: {
gameId: wishlist_items.game_id, id: wishlist_items.id,
gameName: games.name, gameId: wishlist_items.game_id,
gameThumbUrl: games.thumb_url gameName: games.name,
}, gameThumbUrl: games.thumb_url,
}).from(wishlists) },
.leftJoin(wishlist_items, eq(wishlists.id, wishlist_items.wishlist_id)) })
.leftJoin(games, eq(games.id, wishlist_items.game_id)) .from(wishlists)
.where(eq(wishlists.id, params.id)); .leftJoin(wishlist_items, eq(wishlists.id, wishlist_items.wishlist_id))
.leftJoin(games, eq(games.id, wishlist_items.game_id))
.where(eq(wishlists.id, params.id));
return { return {
wishlist wishlist,
}; };
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -46,27 +48,27 @@ export const actions: Actions = {
if (!params?.id) { if (!params?.id) {
throw fail(400, { throw fail(400, {
message: 'Invalid Request' message: 'Invalid Request',
}); });
} }
const game = await db.query.games.findFirst({ const game = await db.query.games.findFirst({
where: eq(games.id, form.id) where: eq(games.id, form.id),
}); });
if (!game) { if (!game) {
return fail(400, { return fail(400, {
message: 'Game not found' message: 'Game not found',
}); });
} }
const wishlist = await db.query.wishlists.findFirst({ const wishlist = await db.query.wishlists.findFirst({
where: eq(wishlists.id, params.id) where: eq(wishlists.id, params.id),
}); });
if (wishlist?.user_id !== locals.user.id) { if (wishlist?.user_id !== locals.user.id) {
return fail(401, { return fail(401, {
message: 'Unauthorized' message: 'Unauthorized',
}); });
} }
@ -76,17 +78,17 @@ export const actions: Actions = {
const wishlistItem = await db.insert(wishlist_items).values({ const wishlistItem = await db.insert(wishlist_items).values({
game_id: game.id, game_id: game.id,
wishlist_id: wishlist.id wishlist_id: wishlist.id,
}); });
if (!wishlistItem) { if (!wishlistItem) {
return fail(500, { return fail(500, {
message: 'Something went wrong' message: 'Something went wrong',
}); });
} }
return { return {
form form,
}; };
}, },
// Create new wishlist // Create new wishlist
@ -96,15 +98,15 @@ export const actions: Actions = {
} }
}, },
// Delete a wishlist // Delete a wishlist
delete: async ({ locals}) => { delete: async ({ locals }) => {
if (!locals.user) { if (!locals.user) {
throw fail(401); throw fail(401);
} }
}, },
// Remove game from a wishlist // Remove game from a wishlist
remove: async ({ locals }) => { remove: async ({ locals }) => {
if (!locals.user) { if (!locals.user) {
throw fail(401); throw fail(401);
} }
} },
}; };

View file

@ -6,9 +6,9 @@ import { message, setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { changeEmailSchema, profileSchema } from '$lib/validations/account'; import { changeEmailSchema, profileSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle'; import db from '../../../../db';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { users } from '../../../../schema'; import { users } from '$db/schema';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
if (!event.locals.user) { if (!event.locals.user) {

View file

@ -5,10 +5,10 @@ import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
import type { PageServerLoad } from '../../../$types'; import type { PageServerLoad } from '../../../$types';
import db from '$lib/drizzle'; import db from '../../../../../../../db';
import { changeUserPasswordSchema } from '$lib/validations/account'; import { changeUserPasswordSchema } from '$lib/validations/account';
import { lucia } from '$lib/server/auth.js'; import { lucia } from '$lib/server/auth.js';
import { users } from '../../../../../../../schema'; import { users } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import type { Cookie } from 'lucia'; import type { Cookie } from 'lucia';
@ -23,10 +23,10 @@ export const load: PageServerLoad = async (event) => {
form.data = { form.data = {
current_password: '', current_password: '',
password: '', password: '',
confirm_password: '' confirm_password: '',
}; };
return { return {
form form,
}; };
}; };
@ -36,7 +36,7 @@ export const actions: Actions = {
if (!form.valid) { if (!form.valid) {
return fail(400, { return fail(400, {
form form,
}); });
} }
@ -52,7 +52,7 @@ export const actions: Actions = {
const user = event.locals.user; const user = event.locals.user;
const dbUser = await db.query.users.findFirst({ const dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id) where: eq(users.id, user.id),
}); });
if (!dbUser?.hashed_password) { if (!dbUser?.hashed_password) {
@ -61,13 +61,13 @@ export const actions: Actions = {
form.data.current_password = ''; form.data.current_password = '';
return setError( return setError(
form, form,
'Error occurred. Please try again or contact support if you need further help.' 'Error occurred. Please try again or contact support if you need further help.',
); );
} }
const currentPasswordVerified = await new Argon2id().verify( const currentPasswordVerified = await new Argon2id().verify(
dbUser.hashed_password, dbUser.hashed_password,
form.data.current_password form.data.current_password,
); );
if (!currentPasswordVerified) { if (!currentPasswordVerified) {
@ -86,7 +86,7 @@ export const actions: Actions = {
.set({ hashed_password: hashedPassword }) .set({ hashed_password: hashedPassword })
.where(eq(users.id, user.id)); .where(eq(users.id, user.id));
await lucia.createSession(user.id, { await lucia.createSession(user.id, {
country: event.locals.session?.ipCountry ?? 'unknown' country: event.locals.session?.ipCountry ?? 'unknown',
}); });
sessionCookie = lucia.createBlankSessionCookie(); sessionCookie = lucia.createBlankSessionCookie();
} catch (e) { } catch (e) {
@ -98,23 +98,23 @@ export const actions: Actions = {
} }
event.cookies.set(sessionCookie.name, sessionCookie.value, { event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.', path: '.',
...sessionCookie.attributes ...sessionCookie.attributes,
}); });
const message = { const message = {
type: 'success', type: 'success',
message: 'Password Updated. Please sign in.' message: 'Password Updated. Please sign in.',
} as const; } as const;
redirect(302, '/login', message, event); redirect(302, '/login', message, event);
} }
return setError( return setError(
form, form,
'Error occurred. Please try again or contact support if you need further help.' 'Error occurred. Please try again or contact support if you need further help.',
); );
// TODO: Add toast instead? // TODO: Add toast instead?
// form.data.password = ''; // form.data.password = '';
// form.data.confirm_password = ''; // form.data.confirm_password = '';
// form.data.current_password = ''; // form.data.current_password = '';
// return message(form, 'Profile updated successfully.'); // return message(form, 'Profile updated successfully.');
} },
}; };

View file

@ -11,8 +11,8 @@ import { redirect, setFlash } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from '../../$types'; import type { PageServerLoad } from '../../$types';
import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account'; import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle'; import db from '../../../../../../db';
import { recovery_codes, users } from '../../../../../../schema'; import { recovery_codes, users } from '$db/schema';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema)); const addTwoFactorForm = await superValidate(event, zod(addTwoFactorSchema));

View file

@ -1,11 +1,11 @@
import db from '$lib/drizzle'; import db from '../../../../../../../db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
import { alphabet, generateRandomString } from 'oslo/crypto'; import { alphabet, generateRandomString } from 'oslo/crypto';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages'; import { notSignedInMessage } from '$lib/flashMessages';
import type { PageServerLoad } from '../../../$types'; import type { PageServerLoad } from '../../../$types';
import { recovery_codes, users } from '../../../../../../../schema'; import { recovery_codes, users } from '$db/schema';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const user = event.locals.user; const user = event.locals.user;

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { modifyListGameSchema } from '$lib/validations/zod-schemas'; import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '$lib/drizzle.js'; import db from '../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js'; import { notSignedInMessage } from '$lib/flashMessages.js';
import { games, wishlist_items, wishlists } from '../../../../schema.js'; import { games, wishlist_items, wishlists } from '$db/schema';
export async function load(event) { export async function load(event) {
const user = event.locals.user; const user = event.locals.user;

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server'; import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { modifyListGameSchema } from '$lib/validations/zod-schemas'; import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '$lib/drizzle.js'; import db from '../../../../../db';
import { notSignedInMessage } from '$lib/flashMessages.js'; import { notSignedInMessage } from '$lib/flashMessages.js';
import { games, wishlist_items, wishlists } from '../../../../../schema.js'; import { games, wishlist_items, wishlists } from '$db/schema';
export async function load(event) { export async function load(event) {
const { params, locals } = event; const { params, locals } = event;

View file

@ -1,8 +1,8 @@
import type { MetaTagsProps } from 'svelte-meta-tags'; import type { MetaTagsProps } from 'svelte-meta-tags';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import db from '$lib/drizzle'; import db from '../../db';
import { collections, wishlists } from '../../schema'; import { collections, wishlists } from '$db/schema';
export const load: PageServerLoad = async ({ locals, url }) => { export const load: PageServerLoad = async ({ locals, url }) => {
const image = { const image = {

View file

@ -6,9 +6,16 @@ import { createMechanic } from '$lib/utils/db/mechanicUtils';
import { createPublisher } from '$lib/utils/db/publisherUtils'; import { createPublisher } from '$lib/utils/db/publisherUtils';
import { createExpansion } from '$lib/utils/db/expansionUtils'; import { createExpansion } from '$lib/utils/db/expansionUtils';
import { createOrUpdateGame } from '$lib/utils/db/gameUtils'; import { createOrUpdateGame } from '$lib/utils/db/gameUtils';
import db from '$lib/drizzle'; import db from '../../../../db';
import { and, eq } from 'drizzle-orm'; import { and, eq } from 'drizzle-orm';
import { collection_items, collections, expansions, games, wishlist_items, wishlists } from '../../../../schema'; import {
collection_items,
collections,
expansions,
games,
wishlist_items,
wishlists,
} from '$db/schema';
export const load: PageServerLoad = async ({ params, locals, fetch }) => { export const load: PageServerLoad = async ({ params, locals, fetch }) => {
try { try {
@ -22,32 +29,32 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
publisher: { publisher: {
columns: { columns: {
id: true, id: true,
name: true name: true,
} },
} },
} },
}, },
mechanics_to_games: { mechanics_to_games: {
with: { with: {
mechanic: { mechanic: {
columns: { columns: {
id: true, id: true,
name: true name: true,
} },
} },
} },
}, },
categories_to_games: { categories_to_games: {
with: { with: {
category: { category: {
columns: { columns: {
id: true, id: true,
name: true name: true,
} },
} },
} },
}, },
} },
}); });
console.log('found game', game); console.log('found game', game);
@ -71,10 +78,10 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
columns: { columns: {
id: true, id: true,
name: true, name: true,
thumb_url: true thumb_url: true,
} },
} },
} },
}); });
let collectionItem; let collectionItem;
@ -87,8 +94,11 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
// TODO: Select wishlist items based on wishlist // TODO: Select wishlist items based on wishlist
if (wishlist) { if (wishlist) {
wishlistItem = await db.query.wishlist_items.findFirst({ wishlistItem = await db.query.wishlist_items.findFirst({
where: and(eq(wishlist_items.wishlist_id, wishlist.id), eq(wishlist_items.game_id, game.id)), where: and(
}) eq(wishlist_items.wishlist_id, wishlist.id),
eq(wishlist_items.game_id, game.id),
),
});
} }
const collection = await db.query.collections.findFirst({ const collection = await db.query.collections.findFirst({
@ -99,8 +109,11 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
if (collection) { if (collection) {
collectionItem = await db.query.collection_items.findFirst({ collectionItem = await db.query.collection_items.findFirst({
where: and(eq(collection_items.collection_id, collection.id), eq(collection_items.game_id, game.id)), where: and(
}) eq(collection_items.collection_id, collection.id),
eq(collection_items.game_id, game.id),
),
});
} }
} }
@ -122,7 +135,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFetch: Function) { async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFetch: Function) {
console.log( console.log(
`Retrieving full external game details for external id: ${game.external_id} with name ${game.name}` `Retrieving full external game details for external id: ${game.external_id} with name ${game.name}`,
); );
const externalGameResponse = await eventFetch(`/api/external/game/${game.external_id}`); const externalGameResponse = await eventFetch(`/api/external/game/${game.external_id}`);
if (externalGameResponse.ok) { if (externalGameResponse.ok) {
@ -134,7 +147,7 @@ async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFet
for (const externalCategory of externalGame.categories) { for (const externalCategory of externalGame.categories) {
const category = await createCategory(locals, externalCategory, externalGame.external_id); const category = await createCategory(locals, externalCategory, externalGame.external_id);
categories.push({ categories.push({
id: category.id id: category.id,
}); });
} }
for (const externalMechanic of externalGame.mechanics) { for (const externalMechanic of externalGame.mechanics) {
@ -151,7 +164,7 @@ async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFet
if (externalExpansion?.inbound === true) { if (externalExpansion?.inbound === true) {
createExpansion(locals, externalExpansion); createExpansion(locals, externalExpansion);
} else { } else {
createExpansion(locals,externalExpansion); createExpansion(locals, externalExpansion);
} }
} }

View file

@ -7,10 +7,10 @@ import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server'; import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '$lib/drizzle'; import db from '../../../db';
import { lucia } from '$lib/server/auth'; import { lucia } from '$lib/server/auth';
import { signInSchema } from '$lib/validations/auth'; import { signInSchema } from '$lib/validations/auth';
import { users, recovery_codes } from '../../../schema'; import { users, recovery_codes } from '$db/schema';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {

View file

@ -1,21 +1,21 @@
import {fail, error, type Actions} from '@sveltejs/kit'; import { fail, error, type Actions } from '@sveltejs/kit';
import {Argon2id} from 'oslo/password'; import { Argon2id } from 'oslo/password';
import {eq} from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import {zod} from 'sveltekit-superforms/adapters'; import { zod } from 'sveltekit-superforms/adapters';
import {setError, superValidate} from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import {redirect} from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import {RateLimiter} from 'sveltekit-rate-limiter/server'; import { RateLimiter } from 'sveltekit-rate-limiter/server';
import type {PageServerLoad} from './$types'; import type { PageServerLoad } from './$types';
import {lucia} from '$lib/server/auth'; import { lucia } from '$lib/server/auth';
import {signUpSchema} from '$lib/validations/auth'; import { signUpSchema } from '$lib/validations/auth';
import {add_user_to_role} from '$server/roles'; import { add_user_to_role } from '$server/roles';
import db from '$lib/drizzle'; import db from '../../../db';
import {collections, users, wishlists} from '../../../schema'; import { collections, users, wishlists } from '$db/schema';
import {createId as cuid2} from '@paralleldrive/cuid2'; import { createId as cuid2 } from '@paralleldrive/cuid2';
const limiter = new RateLimiter({ const limiter = new RateLimiter({
// A rate is defined by [number, unit] // A rate is defined by [number, unit]
IPUA: [5, 'm'] IPUA: [5, 'm'],
}); });
const signUpDefaults = { const signUpDefaults = {
@ -25,7 +25,7 @@ const signUpDefaults = {
username: '', username: '',
password: '', password: '',
confirm_password: '', confirm_password: '',
terms: true terms: true,
}; };
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
@ -37,14 +37,14 @@ export const load: PageServerLoad = async (event) => {
// ); // );
if (event.locals.user) { if (event.locals.user) {
const message = {type: 'success', message: 'You are already signed in'} as const; const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event); throw redirect('/', message, event);
} }
return { return {
form: await superValidate(zod(signUpSchema), { form: await superValidate(zod(signUpSchema), {
defaults: signUpDefaults defaults: signUpDefaults,
}) }),
}; };
}; };
@ -59,7 +59,7 @@ export const actions: Actions = {
form.data.password = ''; form.data.password = '';
form.data.confirm_password = ''; form.data.confirm_password = '';
return fail(400, { return fail(400, {
form form,
}); });
} }
@ -69,7 +69,7 @@ export const actions: Actions = {
console.log('Check if user already exists'); console.log('Check if user already exists');
const existing_user = await db.query.users.findFirst({ const existing_user = await db.query.users.findFirst({
where: eq(users.username, form.data.username) where: eq(users.username, form.data.username),
}); });
if (existing_user) { if (existing_user) {
@ -81,41 +81,41 @@ export const actions: Actions = {
const hashedPassword = await new Argon2id().hash(form.data.password); const hashedPassword = await new Argon2id().hash(form.data.password);
const user = await db const user = await db
.insert(users) .insert(users)
.values({ .values({
username: form.data.username, username: form.data.username,
hashed_password: hashedPassword, hashed_password: hashedPassword,
email: form.data.email, email: form.data.email,
first_name: form.data.firstName ?? '', first_name: form.data.firstName ?? '',
last_name: form.data.lastName ?? '', last_name: form.data.lastName ?? '',
verified: false, verified: false,
receive_email: false, receive_email: false,
theme: 'system', theme: 'system',
two_factor_secret: '', two_factor_secret: '',
two_factor_enabled: false two_factor_enabled: false,
}) })
.returning(); .returning();
console.log('signup user', user); console.log('signup user', user);
if (!user || user.length === 0) { if (!user || user.length === 0) {
return fail(400, { return fail(400, {
form, form,
message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}` message: `Could not create your account. Please try again. If the problem persists, please contact support. Error ID: ${cuid2()}`,
}); });
} }
add_user_to_role(user[0].id, 'user', true); add_user_to_role(user[0].id, 'user', true);
await db.insert(collections).values({ await db.insert(collections).values({
user_id: user[0].id user_id: user[0].id,
}); });
await db.insert(wishlists).values({ await db.insert(wishlists).values({
user_id: user[0].id user_id: user[0].id,
}); });
try { try {
session = await lucia.createSession(user[0].id, { session = await lucia.createSession(user[0].id, {
ip_country: event.locals.ip, ip_country: event.locals.ip,
ip_address: event.locals.country ip_address: event.locals.country,
}); });
sessionCookie = lucia.createSessionCookie(session.id); sessionCookie = lucia.createSessionCookie(session.id);
} catch (e: any) { } catch (e: any) {
@ -126,7 +126,7 @@ export const actions: Actions = {
console.log(e); console.log(e);
const message = { const message = {
type: 'error', type: 'error',
message: 'Unable to create your account. Please try again.' message: 'Unable to create your account. Please try again.',
}; };
form.data.password = ''; form.data.password = '';
form.data.confirm_password = ''; form.data.confirm_password = '';
@ -135,11 +135,11 @@ export const actions: Actions = {
event.cookies.set(sessionCookie.name, sessionCookie.value, { event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.', path: '.',
...sessionCookie.attributes ...sessionCookie.attributes,
}); });
redirect(302, '/'); redirect(302, '/');
// const message = { type: 'success', message: 'Signed Up!' } as const; // const message = { type: 'success', message: 'Signed Up!' } as const;
// throw flashRedirect(message, event); // throw flashRedirect(message, event);
} },
}; };

View file

@ -1,7 +1,7 @@
import db from '$lib/drizzle.js'; import db from '../../../../db';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { users } from '../../../../schema.js'; import { users } from '$db/schema';
import { createPasswordResetToken } from '$lib/server/auth-utils.js'; import { createPasswordResetToken } from '$lib/server/auth-utils.js';
import { PUBLIC_SITE_URL } from '$env/static/public'; import { PUBLIC_SITE_URL } from '$env/static/public';
@ -13,11 +13,13 @@ export async function POST({ locals, request }) {
} }
const user = await db.query.users.findFirst({ const user = await db.query.users.findFirst({
where: eq(users.email, email) where: eq(users.email, email),
}); });
if (!user) { if (!user) {
error(200, { message: 'Email sent! Please check your email for a link to reset your password.' }); error(200, {
message: 'Email sent! Please check your email for a link to reset your password.',
});
} }
const verificationToken = await createPasswordResetToken(user.id); const verificationToken = await createPasswordResetToken(user.id);
@ -27,6 +29,6 @@ export async function POST({ locals, request }) {
console.log('Verification link: ' + verificationLink); console.log('Verification link: ' + verificationLink);
return new Response(null, { return new Response(null, {
status: 200 status: 200,
}); });
} }

View file

@ -1,6 +1,6 @@
import db from '$lib/drizzle.js'; import db from '../../../../../db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { password_reset_tokens, users } from '../../../../../schema.js'; import { password_reset_tokens, users } from '$db/schema';
import { isWithinExpirationDate } from 'oslo'; import { isWithinExpirationDate } from 'oslo';
import { lucia } from '$lib/server/auth.js'; import { lucia } from '$lib/server/auth.js';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
@ -10,34 +10,31 @@ export async function POST({ request, params }) {
if (typeof password !== 'string' || password.length < 8) { if (typeof password !== 'string' || password.length < 8) {
return new Response(null, { return new Response(null, {
status: 400 status: 400,
}); });
} }
const verificationToken = params.token; const verificationToken = params.token;
const token = await db.query.password_reset_tokens.findFirst({ const token = await db.query.password_reset_tokens.findFirst({
where: eq(password_reset_tokens.id, verificationToken) where: eq(password_reset_tokens.id, verificationToken),
}); });
if (!token) { if (!token) {
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken)); await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken));
return new Response(null, { return new Response(null, {
status: 400 status: 400,
}); });
} }
if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) { if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) {
return new Response(null, { return new Response(null, {
status: 400 status: 400,
}); });
} }
await lucia.invalidateUserSessions(token.user_id); await lucia.invalidateUserSessions(token.user_id);
const hashPassword = await new Argon2id().hash(password); const hashPassword = await new Argon2id().hash(password);
await db await db.update(users).set({ hashed_password: hashPassword }).where(eq(users.id, token.user_id));
.update(users)
.set({ hashed_password: hashPassword })
.where(eq(users.id, token.user_id));
const session = await lucia.createSession(token.user_id, {}); const session = await lucia.createSession(token.user_id, {});
const sessionCookie = lucia.createSessionCookie(session.id); const sessionCookie = lucia.createSessionCookie(session.id);
@ -45,8 +42,8 @@ export async function POST({ request, params }) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
Location: "/", Location: '/',
"Set-Cookie": sessionCookie.serialize() 'Set-Cookie': sessionCookie.serialize(),
} },
}); });
} }

View file

@ -1,7 +1,7 @@
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js'; import db from '../../../../../db';
import { collection_items, users } from '../../../../../schema.js'; import { collection_items, users } from '$db/schema';
// Search a user's collection // Search a user's collection
export async function GET({ url, locals, params }) { export async function GET({ url, locals, params }) {
@ -20,7 +20,7 @@ export async function GET({ url, locals, params }) {
} }
const collection = await db.query.collections.findFirst({ const collection = await db.query.collections.findFirst({
where: eq(users.id, locals?.user?.id) where: eq(users.id, locals?.user?.id),
}); });
console.log('collection', collection); console.log('collection', collection);
@ -37,12 +37,13 @@ export async function GET({ url, locals, params }) {
columns: { columns: {
id: true, id: true,
name: true, name: true,
thumb_url: true thumb_url: true,
}, },
} },
}, },
orderBy: (collection_items, { asc, desc }) => { orderBy: (collection_items, { asc, desc }) => {
const dbSort = sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played; const dbSort =
sort === 'dateAdded' ? collection_items.created_at : collection_items.times_played;
if (order === 'asc') { if (order === 'asc') {
return asc(dbSort); return asc(dbSort);
} else { } else {
@ -50,7 +51,7 @@ export async function GET({ url, locals, params }) {
} }
}, },
offset: skip, offset: skip,
limit limit,
}); });
return json(userCollectionItems); return json(userCollectionItems);

View file

@ -1,7 +1,7 @@
import db from '$lib/drizzle.js'; import db from '../../../../db';
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import { asc, count } from 'drizzle-orm'; import { asc, count } from 'drizzle-orm';
import { games, type Games } from '../../../../schema.js'; import { games, type Games } from '$db/schema';
export const GET = async ({ url }) => { export const GET = async ({ url }) => {
const searchParams = Object.fromEntries(url.searchParams); const searchParams = Object.fromEntries(url.searchParams);
@ -14,12 +14,13 @@ export const GET = async ({ url }) => {
try { try {
const totalGames = await db const totalGames = await db
.select({ .select({
value: count(games.id) value: count(games.id),
}) })
.from(games); .from(games);
const numberOfGames = totalGames[0].value || 0; const numberOfGames = totalGames[0].value || 0;
const randomIndex = Math.floor(Math.random() * numberOfGames); const randomIndex = Math.floor(Math.random() * numberOfGames);
const randomGames: Games[] = await db.select() const randomGames: Games[] = await db
.select()
.from(games) .from(games)
.orderBy(asc(games.id)) .orderBy(asc(games.id))
.limit(limit) .limit(limit)
@ -29,4 +30,4 @@ export const GET = async ({ url }) => {
console.error(e); console.error(e);
throw error(500, { message: 'Something went wrong' }); throw error(500, { message: 'Something went wrong' });
} }
} };

View file

@ -1,8 +1,8 @@
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import db from '$lib/drizzle.js'; import db from '../../../../db';
import {asc, desc, eq, ilike, or } from 'drizzle-orm'; import { asc, desc, eq, ilike, or } from 'drizzle-orm';
import { games } from '../../../../schema.js'; import { games } from '$db/schema';
import kebabCase from "just-kebab-case"; import kebabCase from 'just-kebab-case';
// Search a user's collection // Search a user's collection
export const GET = async ({ url, locals }) => { export const GET = async ({ url, locals }) => {
@ -17,20 +17,21 @@ export const GET = async ({ url, locals }) => {
if (orderBy === 'name') { if (orderBy === 'name') {
orderBy = 'slug'; orderBy = 'slug';
} }
console.log(`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}, orderBy: ${orderBy}`); console.log(
`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}, orderBy: ${orderBy}`,
);
console.log(exact); console.log(exact);
if (exact) { if (exact) {
console.log('Exact Search API'); console.log('Exact Search API');
const game = const game = await db.query.games.findFirst({
await db.query.games.findFirst({ where: eq(games.name, q),
where: eq(games.name, q), columns: {
columns: { id: true,
id: true, name: true,
name: true, slug: true,
slug: true, thumb_url: true,
thumb_url: true },
} });
});
if (!game) { if (!game) {
error(404, { message: 'No games found' }); error(404, { message: 'No games found' });
@ -39,20 +40,19 @@ export const GET = async ({ url, locals }) => {
console.log('Games found in Exact Search API', JSON.stringify(foundGames, null, 2)); console.log('Games found in Exact Search API', JSON.stringify(foundGames, null, 2));
return json(foundGames); return json(foundGames);
} else { } else {
const foundGames = await db.select({ const foundGames =
id: games.id, (await db
name: games.name, .select({
slug: games.slug, id: games.id,
thumb_url: games.thumb_url name: games.name,
}) slug: games.slug,
thumb_url: games.thumb_url,
})
.from(games) .from(games)
.where(or( .where(or(ilike(games.name, `%${q}%`), ilike(games.slug, `%${kebabCase(q)}%`)))
ilike(games.name, `%${q}%`),
ilike(games.slug, `%${kebabCase(q)}%`)
))
.orderBy(getOrderDirection(order)(getOrderBy(orderBy))) .orderBy(getOrderDirection(order)(getOrderBy(orderBy)))
.offset(skip) .offset(skip)
.limit(limit) || []; .limit(limit)) || [];
// const foundGames = await db.select({ // const foundGames = await db.select({
// id: games.id, // id: games.id,
// name: games.name, // name: games.name,
@ -73,7 +73,7 @@ export const GET = async ({ url, locals }) => {
type OrderDirection = 'asc' | 'desc'; type OrderDirection = 'asc' | 'desc';
const getOrderDirection = (direction: OrderDirection) => { const getOrderDirection = (direction: OrderDirection) => {
return direction === 'asc' ? asc: desc; return direction === 'asc' ? asc : desc;
}; };
const getOrderBy = (orderBy: string) => { const getOrderBy = (orderBy: string) => {
@ -85,4 +85,4 @@ const getOrderBy = (orderBy: string) => {
default: default:
return games.slug; return games.slug;
} }
} };

View file

@ -1,10 +1,10 @@
import { createPublisher } from '$lib/utils/db/publisherUtils.js'; import { createPublisher } from '$lib/utils/db/publisherUtils.js';
import type { Publishers } from '../../../schema.js'; import type { Publishers } from '$db/schema';
type PublisherCreate = { type PublisherCreate = {
publisher: Publishers; publisher: Publishers;
externalId: string; externalId: string;
} };
export async function POST({ request, locals }) { export async function POST({ request, locals }) {
const data: PublisherCreate = await request.json(); const data: PublisherCreate = await request.json();
@ -15,7 +15,7 @@ export async function POST({ request, locals }) {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return new Response('Could not create publisher', { return new Response('Could not create publisher', {
status: 500 status: 500,
}); });
} }
} }

View file

@ -1,5 +1,5 @@
import { getPublisher, updatePublisher } from '$lib/utils/db/publisherUtils.js'; import { getPublisher, updatePublisher } from '$lib/utils/db/publisherUtils.js';
import type { Publishers } from '../../../../schema.js'; import type { Publishers } from '$db/schema';
export async function GET({ locals, params }) { export async function GET({ locals, params }) {
try { try {
@ -7,7 +7,7 @@ export async function GET({ locals, params }) {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return new Response('Could not get publishers', { return new Response('Could not get publishers', {
status: 500 status: 500,
}); });
} }
} }
@ -16,4 +16,4 @@ export async function PUT({ locals, params, request }) {
const data: Publishers = await request.json(); const data: Publishers = await request.json();
const publisherId = params.id; const publisherId = params.id;
return await updatePublisher(locals, data, publisherId); return await updatePublisher(locals, data, publisherId);
} }

View file

@ -1,7 +1,7 @@
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js'; import db from '../../../../../db';
import { wishlist_items, wishlists } from '../../../../../schema.js'; import { wishlist_items, wishlists } from '$db/schema';
// Search a user's collection // Search a user's collection
export async function GET({ url, locals, params }) { export async function GET({ url, locals, params }) {
@ -16,12 +16,12 @@ export async function GET({ url, locals, params }) {
if (!locals.user) { if (!locals.user) {
return new Response(null, { return new Response(null, {
status: 401 status: 401,
}); });
} }
const wishlist = await db.query.wishlists.findFirst({ const wishlist = await db.query.wishlists.findFirst({
where: eq(wishlists.user_id, locals?.user?.id) where: eq(wishlists.user_id, locals?.user?.id),
}); });
console.log('wishlist', wishlist); console.log('wishlist', wishlist);
@ -38,9 +38,9 @@ export async function GET({ url, locals, params }) {
columns: { columns: {
id: true, id: true,
name: true, name: true,
thumb_url: true thumb_url: true,
} },
} },
}, },
orderBy: (wishlist_items, { asc, desc }) => { orderBy: (wishlist_items, { asc, desc }) => {
const dbSort = wishlist_items.created_at; const dbSort = wishlist_items.created_at;
@ -51,7 +51,7 @@ export async function GET({ url, locals, params }) {
} }
}, },
offset: skip, offset: skip,
limit limit,
}); });
return json(itemsInWishlist); return json(itemsInWishlist);

View file

@ -1,549 +0,0 @@
import { relations, sql, type InferSelectModel } from 'drizzle-orm';
import {
pgTable,
timestamp,
text,
boolean,
integer,
index,
pgEnum,
primaryKey,
uuid,
} from 'drizzle-orm/pg-core';
import { createId as cuid2 } from '@paralleldrive/cuid2';
import { tsvector } from './tsVector';
// User Related Schemas
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
username: text('username').unique(),
hashed_password: text('hashed_password'),
email: text('email').unique(),
first_name: text('first_name'),
last_name: text('last_name'),
verified: boolean('verified').default(false),
receive_email: boolean('receive_email').default(false),
theme: text('theme').default('system'),
two_factor_secret: text('two_factor_secret').default(''),
two_factor_enabled: boolean('two_factor_enabled').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const recovery_codes = pgTable('recovery_codes', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
code: text('code').notNull(),
used: boolean('used').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type RecoveryCodes = InferSelectModel<typeof recovery_codes>;
export const user_relations = relations(users, ({ many }) => ({
user_roles: many(user_roles),
}));
export type Users = InferSelectModel<typeof users>;
export const sessions = pgTable('sessions', {
id: text('id').primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
expiresAt: timestamp('expires_at', {
withTimezone: true,
mode: 'date',
}).notNull(),
ipCountry: text('ip_country'),
ipAddress: text('ip_address'),
});
export const roles = pgTable('roles', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2())
.notNull(),
name: text('name').unique().notNull(),
});
export type Roles = InferSelectModel<typeof roles>;
export const role_relations = relations(roles, ({ many }) => ({
user_roles: many(user_roles),
}));
export const user_roles = pgTable('user_roles', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
role_id: uuid('role_id')
.notNull()
.references(() => roles.id, { onDelete: 'cascade' }),
primary: boolean('primary').default(false),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const user_role_relations = relations(user_roles, ({ one }) => ({
role: one(roles, {
fields: [user_roles.role_id],
references: [roles.id],
}),
user: one(users, {
fields: [user_roles.user_id],
references: [users.id],
}),
}));
export type UserRoles = InferSelectModel<typeof user_roles>;
export const password_reset_tokens = pgTable('password_reset_tokens', {
id: text('id')
.primaryKey()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expires_at: timestamp('expires_at', {
withTimezone: true,
mode: 'date',
precision: 6,
}),
created_at: timestamp('created_at').notNull().defaultNow(),
});
export const password_reset_token_relations = relations(password_reset_tokens, ({ one }) => ({
user: one(users, {
fields: [password_reset_tokens.user_id],
references: [users.id],
}),
}));
export const collections = pgTable('collections', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Collection'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export const collection_relations = relations(collections, ({ one }) => ({
user: one(users, {
fields: [collections.user_id],
references: [users.id],
}),
}));
export const collection_items = pgTable('collection_items', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
collection_id: uuid('collection_id')
.notNull()
.references(() => collections.id, { onDelete: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
times_played: integer('times_played').default(0),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type CollectionItems = InferSelectModel<typeof collection_items>;
export const collection_item_relations = relations(collection_items, ({ one }) => ({
collection: one(collections, {
fields: [collection_items.collection_id],
references: [collections.id],
}),
game: one(games, {
fields: [collection_items.game_id],
references: [games.id],
}),
}));
export const wishlists = pgTable('wishlists', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull().default('My Wishlist'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Wishlists = InferSelectModel<typeof wishlists>;
export const wishlists_relations = relations(wishlists, ({ one }) => ({
user: one(users, {
fields: [wishlists.user_id],
references: [users.id],
}),
}));
export const wishlist_items = pgTable('wishlist_items', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
wishlist_id: uuid('wishlist_id')
.notNull()
.references(() => wishlists.id, { onDelete: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type WishlistItems = InferSelectModel<typeof wishlist_items>;
export const wishlist_item_relations = relations(wishlist_items, ({ one }) => ({
wishlist: one(wishlists, {
fields: [wishlist_items.wishlist_id],
references: [wishlists.id],
}),
game: one(games, {
fields: [wishlist_items.game_id],
references: [games.id],
}),
}));
// Game and related table schemas
export const externalIdType = pgEnum('external_id_type', [
'game',
'category',
'mechanic',
'publisher',
'designer',
'artist',
]);
export const externalIds = pgTable('external_ids', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
type: externalIdType('type').notNull(),
externalId: text('external_id').notNull(),
});
export type ExternalIds = InferSelectModel<typeof externalIds>;
export const games = pgTable(
'games',
{
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
description: text('description'),
year_published: integer('year_published'),
min_players: integer('min_players'),
max_players: integer('max_players'),
playtime: integer('playtime'),
min_playtime: integer('min_playtime'),
max_playtime: integer('max_playtime'),
min_age: integer('min_age'),
image_url: text('image_url'),
thumb_url: text('thumb_url'),
url: text('url'),
text_searchable_index: tsvector('text_searchable_index'),
last_sync_at: timestamp('last_sync_at', {
withTimezone: true,
mode: 'date',
precision: 6,
}),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
},
(table) => {
return {
text_searchable_idx: index('text_searchable_idx')
.on(table.text_searchable_index)
.using(sql`'gin'`),
};
},
);
export type Games = InferSelectModel<typeof games>;
export const gamesToExternalIds = pgTable(
'games_to_external_ids',
{
gameId: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
gamesToExternalIdsPkey: primaryKey({
columns: [table.gameId, table.externalId],
}),
};
},
);
export const gameRelations = relations(games, ({ many }) => ({
categories_to_games: many(categories_to_games),
mechanics_to_games: many(mechanics_to_games),
publishers_to_games: many(publishers_to_games),
gamesToExternalIds: many(gamesToExternalIds),
}));
export const expansions = pgTable('expansions', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
base_game_id: uuid('base_game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Expansions = InferSelectModel<typeof expansions>;
export const expansion_relations = relations(expansions, ({ one }) => ({
baseGame: one(games, {
fields: [expansions.base_game_id],
references: [games.id],
}),
game: one(games, {
fields: [expansions.game_id],
references: [games.id],
}),
}));
export const publishers = pgTable('publishers', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Publishers = InferSelectModel<typeof publishers>;
export const publishersToExternalIds = pgTable(
'publishers_to_external_ids',
{
publisherId: uuid('publisher_id')
.notNull()
.references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
publishersToExternalIdsPkey: primaryKey({
columns: [table.publisherId, table.externalId],
}),
};
},
);
export const publishers_relations = relations(publishers, ({ many }) => ({
publishers_to_games: many(publishers_to_games),
publishersToExternalIds: many(publishersToExternalIds),
}));
export const categories = pgTable('categories', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Categories = InferSelectModel<typeof categories>;
export const categoriesToExternalIds = pgTable(
'categories_to_external_ids',
{
categoryId: uuid('category_id')
.notNull()
.references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
categoriesToExternalIdsPkey: primaryKey({
columns: [table.categoryId, table.externalId],
}),
};
},
);
export const categories_to_games = pgTable(
'categories_to_games',
{
category_id: uuid('category_id')
.notNull()
.references(() => categories.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
categoriesToGamesPkey: primaryKey({
columns: [table.category_id, table.game_id],
}),
};
},
);
export const categories_to_games_relations = relations(categories_to_games, ({ one }) => ({
category: one(categories, {
fields: [categories_to_games.category_id],
references: [categories.id],
}),
game: one(games, {
fields: [categories_to_games.game_id],
references: [games.id],
}),
}));
export const categories_relations = relations(categories, ({ many }) => ({
categories_to_games: many(categories_to_games),
categoriesToExternalIds: many(categoriesToExternalIds),
}));
export const mechanics = pgTable('mechanics', {
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name'),
slug: text('slug'),
created_at: timestamp('created_at').notNull().defaultNow(),
updated_at: timestamp('updated_at').notNull().defaultNow(),
});
export type Mechanics = InferSelectModel<typeof mechanics>;
export const mechanicsToExternalIds = pgTable(
'mechanics_to_external_ids',
{
mechanicId: uuid('mechanic_id')
.notNull()
.references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
externalId: uuid('external_id')
.notNull()
.references(() => externalIds.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
mechanicsToExternalIdsPkey: primaryKey({
columns: [table.mechanicId, table.externalId],
}),
};
},
);
export const mechanic_relations = relations(mechanics, ({ many }) => ({
mechanics_to_games: many(mechanics_to_games),
mechanicsToExternalIds: many(mechanicsToExternalIds),
}));
export const mechanics_to_games = pgTable(
'mechanics_to_games',
{
mechanic_id: uuid('mechanic_id')
.notNull()
.references(() => mechanics.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
mechanicsToGamesPkey: primaryKey({
columns: [table.mechanic_id, table.game_id],
}),
};
},
);
export const mechanics_to_games_relations = relations(mechanics_to_games, ({ one }) => ({
mechanic: one(mechanics, {
fields: [mechanics_to_games.mechanic_id],
references: [mechanics.id],
}),
game: one(games, {
fields: [mechanics_to_games.game_id],
references: [games.id],
}),
}));
export const publishers_to_games = pgTable(
'publishers_to_games',
{
publisher_id: uuid('publisher_id')
.notNull()
.references(() => publishers.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
game_id: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'restrict', onUpdate: 'cascade' }),
},
(table) => {
return {
publishersToGamesPkey: primaryKey({
columns: [table.publisher_id, table.game_id],
}),
};
},
);
export const publishers_to_games_relations = relations(publishers_to_games, ({ one }) => ({
publisher: one(publishers, {
fields: [publishers_to_games.publisher_id],
references: [publishers.id],
}),
game: one(games, {
fields: [publishers_to_games.game_id],
references: [games.id],
}),
}));

View file

@ -1,11 +1,11 @@
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import db from '$lib/drizzle'; import db from '../db';
import { roles, user_roles } from '../schema'; import { roles, user_roles } from '$db/schema';
export async function add_user_to_role(user_id: string, role_name: string, primary = false) { export async function add_user_to_role(user_id: string, role_name: string, primary = false) {
// Find the role by its name // Find the role by its name
const role = await db.query.roles.findFirst({ const role = await db.query.roles.findFirst({
where: eq(roles.name, role_name) where: eq(roles.name, role_name),
}); });
if (!role || !role.id) { if (!role || !role.id) {
@ -16,6 +16,6 @@ export async function add_user_to_role(user_id: string, role_name: string, prima
return db.insert(user_roles).values({ return db.insert(user_roles).values({
user_id, user_id,
role_id: role.id, role_id: role.id,
primary primary,
}); });
} }

View file

@ -1,17 +1,17 @@
import db from '$lib/drizzle'; import db from '../db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { users, type Users } from '../schema'; import { users, type Users } from '$db/schema';
import { add_user_to_role } from './roles'; import { add_user_to_role } from './roles';
export function create_user(user: Users) { export function create_user(user: Users) {
return db.insert(users).values({ return db.insert(users).values({
username: user.username username: user.username,
}); });
} }
export async function find_or_create_user(user: Users) { export async function find_or_create_user(user: Users) {
const existing_user = await db.query.users.findFirst({ const existing_user = await db.query.users.findFirst({
where: eq(users.username, user.username) where: eq(users.username, user.username),
}); });
if (existing_user) { if (existing_user) {
return existing_user; return existing_user;
@ -30,12 +30,12 @@ export async function find_user_with_roles(user_id: string) {
with: { with: {
role: { role: {
select: { select: {
name: true name: true,
} },
} },
} },
} },
} },
}); });
if (!user_with_roles) { if (!user_with_roles) {
throw new Error('User not found'); throw new Error('User not found');
@ -43,6 +43,6 @@ export async function find_user_with_roles(user_id: string) {
return { return {
...user_with_roles, ...user_with_roles,
roles: user_with_roles.role.map((user_role) => user_role.role.name) roles: user_with_roles.role.map((user_role) => user_role.role.name),
}; };
} }

View file

@ -12,8 +12,8 @@ const config = {
inspector: { inspector: {
toggleKeyCombo: 'control-alt-shift', toggleKeyCombo: 'control-alt-shift',
showToggleButton: 'always', showToggleButton: 'always',
toggleButtonPos: 'bottom-right' toggleButtonPos: 'bottom-right',
} },
}, },
kit: { kit: {
adapter: adapter(), adapter: adapter(),
@ -21,19 +21,20 @@ const config = {
$assets: './src/assets', $assets: './src/assets',
$components: './src/components', $components: './src/components',
'$components/*': 'src/lib/components/*', '$components/*': 'src/lib/components/*',
$db: './src/db',
$server: './src/server', $server: './src/server',
$lib: './src/lib', $lib: './src/lib',
$state: './src/state', $state: './src/state',
$styles: './src/styles', $styles: './src/styles',
$themes: './src/themes' $themes: './src/themes',
} },
}, },
shadcn: { shadcn: {
componentPath: './src/lib/components/ui' componentPath: './src/lib/components/ui',
}, },
compilerOptions: { compilerOptions: {
enableSourcemap: true, enableSourcemap: true,
} },
}; };
export default config; export default config;