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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,11 @@ import {
DATABASE_PASSWORD,
DATABASE_HOST,
DATABASE_DB,
DATABASE_PORT
DATABASE_PORT,
DB_MIGRATING,
DB_SEEDING,
} from '$env/static/private';
import * as schema from '../schema';
import * as schema from './schema';
// create the connection
const pool = new pg.Pool({
@ -16,15 +18,15 @@ const pool = new pg.Pool({
host: DATABASE_HOST,
port: Number(DATABASE_PORT).valueOf(),
database: DATABASE_DB,
ssl: DATABASE_HOST !== 'localhost'
ssl: DATABASE_HOST !== 'localhost',
max: DB_MIGRATING || DB_SEEDING ? 1 : undefined,
});
// user: DATABASE_USER,
// password: DATABASE_PASSWORD,
// host: DATABASE_HOST,
// port: Number(DATABASE_PORT).valueOf(),
// database: DATABASE_DB
const db = drizzle(pool, {
schema,
logger: process.env.NODE_ENV === 'development',
});
const db = drizzle(pool, { schema });
export type db = typeof 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 pg from 'pg';
import * as schema from './schema';
import {Argon2id} from "oslo/password";
import { Argon2id } from 'oslo/password';
// create the connection
const pool = new pg.Pool({
@ -11,7 +11,7 @@ const pool = new pg.Pool({
host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT).valueOf(),
database: process.env.DATABASE_DB,
ssl: process.env.DATABASE_HOST !== 'localhost'
ssl: process.env.DATABASE_HOST !== 'localhost',
});
const db = drizzle(pool, { schema: schema });
@ -40,31 +40,37 @@ console.log('Roles created.');
console.log('Admin Role: ', adminRole);
const adminUser = await db
.insert(schema.users)
.values({
username: `${process.env.ADMIN_USERNAME}`,
email: '',
hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`),
first_name: 'Brad',
last_name: 'S',
verified: true
})
.returning()
.onConflictDoNothing();
.insert(schema.users)
.values({
username: `${process.env.ADMIN_USERNAME}`,
email: '',
hashed_password: await new Argon2id().hash(`${process.env.ADMIN_PASSWORD}`),
first_name: 'Brad',
last_name: 'S',
verified: true,
})
.returning()
.onConflictDoNothing();
console.log('Admin user created.', adminUser);
await db.insert(schema.user_roles).values({
user_id: adminUser[0].id,
role_id: adminRole[0].id
}).onConflictDoNothing();
await db
.insert(schema.user_roles)
.values({
user_id: adminUser[0].id,
role_id: adminRole[0].id,
})
.onConflictDoNothing();
console.log('Admin user given admin role.');
await db.insert(schema.user_roles).values({
user_id: adminUser[0].id,
role_id: userRole[0].id
}).onConflictDoNothing();
await db
.insert(schema.user_roles)
.values({
user_id: adminUser[0].id,
role_id: userRole[0].id,
})
.onConflictDoNothing();
console.log('Admin user given user role.');

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 { MinusCircle, PlusCircle } from "lucide-svelte";
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 collection: CollectionItems;

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { GameType, SavedGameType } from '$lib/types';
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 variant: 'default' | 'compact' = 'default';

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import type { SvelteComponent } from 'svelte';
import { collections } from '../schema';
import { collections } from '$db/schema';
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 kebabCase from 'just-kebab-case';
import { PUBLIC_SITE_URL } from '$env/static/public';
import db from '$lib/drizzle';
import { externalIds, type Mechanics, type Categories, categories, categoriesToExternalIds } from '../../../schema';
import db from '../../../db';
import {
externalIds,
type Mechanics,
type Categories,
categories,
categoriesToExternalIds,
} from '$db/schema';
export async function createCategory(locals: App.Locals, category: Categories, externalId: string) {
if (!category || !externalId || externalId === '') {
@ -12,7 +18,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
try {
const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId)
where: eq(externalIds.externalId, externalId),
});
if (dbExternalId) {
@ -20,7 +26,7 @@ export async function createCategory(locals: App.Locals, category: Categories, e
.select({
id: categories.id,
name: categories.name,
slug: categories.slug
slug: categories.slug,
})
.from(categories)
.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', {
headers: {
'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)
.values({
name: category.name,
slug: kebabCase(category.name ?? category.slug ?? '')
slug: kebabCase(category.name ?? category.slug ?? ''),
})
.returning();
const dbExternalIds = await transaction
.insert(externalIds)
.values({
externalId,
type: 'category'
type: 'category',
})
.returning({ id: externalIds.id });
await transaction.insert(categoriesToExternalIds).values({
categoryId: dbCategory[0].id,
externalId: dbExternalIds[0].id
externalId: dbExternalIds[0].id,
});
});
if (dbCategory.length === 0) {
return new Response('Could not create category', {
status: 500
status: 500,
});
}

View file

@ -1,7 +1,7 @@
import { error } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm';
import db from '$lib/drizzle';
import { type Expansions, expansions } from '../../../schema';
import db from '../../../db';
import { type Expansions, expansions } from '$db/schema';
import { PUBLIC_SITE_URL } from '$env/static/public';
export async function createExpansion(locals: App.Locals, expansion: Expansions) {
@ -10,24 +10,26 @@ export async function createExpansion(locals: App.Locals, expansion: Expansions)
}
try {
const foundExpansion = await db.query.expansions
.findFirst({
where: and(eq(expansions.base_game_id, expansion.base_game_id), eq(expansions.game_id, expansion.game_id)),
columns: {
id: true,
game_id: true,
base_game_id: true
}
});
const foundExpansion = await db.query.expansions.findFirst({
where: and(
eq(expansions.base_game_id, expansion.base_game_id),
eq(expansions.game_id, expansion.game_id),
),
columns: {
id: true,
game_id: true,
base_game_id: true,
},
});
console.log('Expansion already exists', foundExpansion);
if (foundExpansion) {
console.log('Expansion Game ID', foundExpansion.game_id);
return new Response('Expansion already exists', {
headers: {
'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) {
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);
// throw new Error('Something went wrong creating Expansion');
// }
// }
// }

View file

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

View file

@ -1,6 +1,6 @@
import kebabCase from 'just-kebab-case';
import db from '$lib/drizzle';
import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '../../../schema';
import db from '../../../db';
import { externalIds, mechanics, mechanicsToExternalIds, type Mechanics } from '$db/schema';
import { eq } from 'drizzle-orm';
import { error } from '@sveltejs/kit';
import { PUBLIC_SITE_URL } from '$env/static/public';
@ -12,7 +12,7 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
try {
const dbExternalId = await db.query.externalIds.findFirst({
where: eq(externalIds.externalId, externalId)
where: eq(externalIds.externalId, externalId),
});
if (dbExternalId) {
@ -20,7 +20,7 @@ export async function createMechanic(locals: App.Locals, mechanic: Mechanics, ex
.select({
id: mechanics.id,
name: mechanics.name,
slug: mechanics.slug
slug: mechanics.slug,
})
.from(mechanics)
.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', {
headers: {
'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)
.values({
name: mechanic.name,
slug: kebabCase(mechanic.name || mechanic.slug || '')
slug: kebabCase(mechanic.name || mechanic.slug || ''),
})
.returning();
const dbExternalIds = await transaction
.insert(externalIds)
.values({
externalId,
type: 'mechanic'
type: 'mechanic',
})
.returning({ id: externalIds.id });
await transaction.insert(mechanicsToExternalIds).values({
mechanicId: dbMechanics[0].id,
externalId: dbExternalIds[0].id
externalId: dbExternalIds[0].id,
});
});
if (dbMechanics.length === 0) {
return new Response('Could not create mechanic', {
status: 500
status: 500,
});
}

View file

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

View file

@ -1,6 +1,6 @@
import type { GameType, SavedGameType } from '$lib/types';
import kebabCase from 'just-kebab-case';
import type { Games } from '../../schema';
import type { Games } from '$db/schema';
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
return {
@ -9,7 +9,7 @@ export function convertToSavedGame(game: GameType | SavedGameType): SavedGameTyp
thumb_url: game.thumb_url,
players: game.players,
playtime: game.playtime,
searchTerms: `${game.name.toLowerCase()}`
searchTerms: `${game.name.toLowerCase()}`,
};
}
@ -39,7 +39,7 @@ export function mapSavedGameToGame(game: SavedGameType): GameType {
description: '',
description_preview: '',
players,
playtime
playtime,
};
}
@ -56,6 +56,6 @@ export function mapAPIGameToBoredGame(game: GameType): Games {
min_playtime: game.min_playtime,
max_playtime: game.max_playtime,
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 { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle';
import { user_roles } from '../../../../schema';
import db from '../../../../db';
import { user_roles } from '$db/schema';
export const load = loadFlash(async (event) => {
const { locals } = event;
@ -16,10 +16,10 @@ export const load = loadFlash(async (event) => {
with: {
role: {
columns: {
name: true
}
}
}
name: true,
},
},
},
});
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 type { PageServerLoad } from "./$types";
import { notSignedInMessage } from "$lib/flashMessages";
import db from "$lib/drizzle";
import { redirect } from 'sveltekit-flash-message/server';
import type { PageServerLoad } from './$types';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '../../../../../db';
export const load: PageServerLoad = async (event) => {
// TODO: Ensure admin user
if (!event.locals.user) {
redirect(302, '/login', notSignedInMessage, event);
}
const users = await db.query
.users
.findMany({
limit: 10,
offset: 0
});
const users = await db.query.users.findMany({
limit: 10,
offset: 0,
});
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 type { PageServerLoad } from './$types';
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import { roles, user_roles, users } from '../../../../../../schema';
import db from '../../../../../../db';
import { roles, user_roles, users } from '$db/schema';
export const load: PageServerLoad = async (event) => {
const { params } = event;
@ -22,16 +22,16 @@ export const load: PageServerLoad = async (event) => {
role: {
columns: {
name: true,
cuid: true
}
}
}
}
}
cuid: true,
},
},
},
},
},
});
const containsAdminRole = foundUser?.user_roles?.some(
(user_role) => user_role?.role?.name === 'admin'
(user_role) => user_role?.role?.name === 'admin',
);
if (!containsAdminRole) {
console.log('Not an admin');
@ -45,14 +45,14 @@ export const load: PageServerLoad = async (event) => {
where: not(inArray(roles.cuid, currentRoleIds)),
columns: {
name: true,
cuid: true
}
cuid: true,
},
});
}
return {
user: foundUser,
availableRoles
availableRoles,
};
};
@ -71,10 +71,10 @@ export const actions = {
role: {
columns: {
name: true,
cuid: true
}
}
}
cuid: true,
},
},
},
});
console.log('userRoles', userRoles);
@ -88,13 +88,13 @@ export const actions = {
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
where: eq(roles.cuid, role?.toString() ?? ''),
});
console.log('dbRole', dbRole);
if (dbRole) {
await db.insert(user_roles).values({
user_id: user.id,
role_id: dbRole.id
role_id: dbRole.id,
});
redirect({ type: 'success', message: `Successfully added role ${dbRole.name}!` }, event);
} else {
@ -114,10 +114,10 @@ export const actions = {
role: {
columns: {
name: true,
cuid: true
}
}
}
cuid: true,
},
},
},
});
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
@ -128,7 +128,7 @@ export const actions = {
const data = await request.formData();
const role = data.get('role');
const dbRole = await db.query.roles.findFirst({
where: eq(roles.cuid, role?.toString() ?? '')
where: eq(roles.cuid, role?.toString() ?? ''),
});
console.log('dbRole', dbRole);
if (dbRole) {
@ -139,5 +139,5 @@ export const actions = {
} else {
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 * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import DataTableCheckbox from "./user-table-checkbox.svelte";
import type { Users } from '../../../../../schema';
import type { Users } from '$db/schema';
export let users: Users[] = [];

View file

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

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
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 { collections, games, collection_items } from '../../../../../schema.js';
import { collections, games, collection_items } from '$db/schema';
import { search_schema } from '$lib/zodValidation';
import type { UICollection } from '$lib/types';

View file

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

View file

@ -1,10 +1,10 @@
import { type Actions, fail, redirect } from "@sveltejs/kit";
import { eq } from "drizzle-orm";
import { zod } from "sveltekit-superforms/adapters";
import { type Actions, fail, redirect } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import db from "$lib/drizzle.js";
import { modifyListGameSchema } from "$lib/validations/zod-schemas";
import { games, wishlist_items, wishlists } from "../../../../../schema.js";
import db from '../../../../../db';
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import { games, wishlist_items, wishlists } from '$db/schema';
export async function load({ params, locals }) {
const user = locals.user;
@ -13,20 +13,22 @@ export async function load({ params, locals }) {
}
try {
const wishlist = await db.select({
wishlistId: wishlists.id,
wishlistItems: {
id: wishlist_items.id,
gameId: wishlist_items.game_id,
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))
.where(eq(wishlists.id, params.id));
const wishlist = await db
.select({
wishlistId: wishlists.id,
wishlistItems: {
id: wishlist_items.id,
gameId: wishlist_items.game_id,
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))
.where(eq(wishlists.id, params.id));
return {
wishlist
wishlist,
};
} catch (e) {
console.error(e);
@ -46,27 +48,27 @@ export const actions: Actions = {
if (!params?.id) {
throw fail(400, {
message: 'Invalid Request'
message: 'Invalid Request',
});
}
const game = await db.query.games.findFirst({
where: eq(games.id, form.id)
where: eq(games.id, form.id),
});
if (!game) {
return fail(400, {
message: 'Game not found'
message: 'Game not found',
});
}
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) {
return fail(401, {
message: 'Unauthorized'
message: 'Unauthorized',
});
}
@ -76,17 +78,17 @@ export const actions: Actions = {
const wishlistItem = await db.insert(wishlist_items).values({
game_id: game.id,
wishlist_id: wishlist.id
wishlist_id: wishlist.id,
});
if (!wishlistItem) {
return fail(500, {
message: 'Something went wrong'
message: 'Something went wrong',
});
}
return {
form
form,
};
},
// Create new wishlist
@ -96,15 +98,15 @@ export const actions: Actions = {
}
},
// Delete a wishlist
delete: async ({ locals}) => {
delete: async ({ locals }) => {
if (!locals.user) {
throw fail(401);
}
},
// Remove game from a wishlist
remove: async ({ locals }) => {
remove: async ({ locals }) => {
if (!locals.user) {
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 { changeEmailSchema, profileSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import db from '../../../../db';
import type { PageServerLoad } from './$types';
import { users } from '../../../../schema';
import { users } from '$db/schema';
export const load: PageServerLoad = async (event) => {
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 { Argon2id } from 'oslo/password';
import type { PageServerLoad } from '../../../$types';
import db from '$lib/drizzle';
import db from '../../../../../../../db';
import { changeUserPasswordSchema } from '$lib/validations/account';
import { lucia } from '$lib/server/auth.js';
import { users } from '../../../../../../../schema';
import { users } from '$db/schema';
import { notSignedInMessage } from '$lib/flashMessages';
import type { Cookie } from 'lucia';
@ -23,10 +23,10 @@ export const load: PageServerLoad = async (event) => {
form.data = {
current_password: '',
password: '',
confirm_password: ''
confirm_password: '',
};
return {
form
form,
};
};
@ -36,7 +36,7 @@ export const actions: Actions = {
if (!form.valid) {
return fail(400, {
form
form,
});
}
@ -52,7 +52,7 @@ export const actions: Actions = {
const user = event.locals.user;
const dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id)
where: eq(users.id, user.id),
});
if (!dbUser?.hashed_password) {
@ -61,13 +61,13 @@ export const actions: Actions = {
form.data.current_password = '';
return setError(
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(
dbUser.hashed_password,
form.data.current_password
form.data.current_password,
);
if (!currentPasswordVerified) {
@ -86,7 +86,7 @@ export const actions: Actions = {
.set({ hashed_password: hashedPassword })
.where(eq(users.id, user.id));
await lucia.createSession(user.id, {
country: event.locals.session?.ipCountry ?? 'unknown'
country: event.locals.session?.ipCountry ?? 'unknown',
});
sessionCookie = lucia.createBlankSessionCookie();
} catch (e) {
@ -98,23 +98,23 @@ export const actions: Actions = {
}
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes
...sessionCookie.attributes,
});
const message = {
type: 'success',
message: 'Password Updated. Please sign in.'
message: 'Password Updated. Please sign in.',
} as const;
redirect(302, '/login', message, event);
}
return setError(
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?
// form.data.password = '';
// form.data.confirm_password = '';
// form.data.current_password = '';
// 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 { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account';
import { notSignedInMessage } from '$lib/flashMessages';
import db from '$lib/drizzle';
import { recovery_codes, users } from '../../../../../../schema';
import db from '../../../../../../db';
import { recovery_codes, users } from '$db/schema';
export const load: PageServerLoad = async (event) => {
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 { Argon2id } from 'oslo/password';
import { alphabet, generateRandomString } from 'oslo/crypto';
import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages';
import type { PageServerLoad } from '../../../$types';
import { recovery_codes, users } from '../../../../../../../schema';
import { recovery_codes, users } from '$db/schema';
export const load: PageServerLoad = async (event) => {
const user = event.locals.user;

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '$lib/drizzle.js';
import db from '../../../../db';
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) {
const user = event.locals.user;

View file

@ -4,9 +4,9 @@ import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
import db from '$lib/drizzle.js';
import db from '../../../../../db';
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) {
const { params, locals } = event;

View file

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

View file

@ -6,9 +6,16 @@ import { createMechanic } from '$lib/utils/db/mechanicUtils';
import { createPublisher } from '$lib/utils/db/publisherUtils';
import { createExpansion } from '$lib/utils/db/expansionUtils';
import { createOrUpdateGame } from '$lib/utils/db/gameUtils';
import db from '$lib/drizzle';
import db from '../../../../db';
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 }) => {
try {
@ -22,32 +29,32 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
publisher: {
columns: {
id: true,
name: true
}
}
}
name: true,
},
},
},
},
mechanics_to_games: {
with: {
mechanic: {
columns: {
id: true,
name: true
}
}
}
name: true,
},
},
},
},
categories_to_games: {
with: {
category: {
columns: {
id: true,
name: true
}
}
}
name: true,
},
},
},
},
}
},
});
console.log('found game', game);
@ -71,10 +78,10 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
columns: {
id: true,
name: true,
thumb_url: true
}
}
}
thumb_url: true,
},
},
},
});
let collectionItem;
@ -87,8 +94,11 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
// TODO: Select wishlist items based on wishlist
if (wishlist) {
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({
@ -99,8 +109,11 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
if (collection) {
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) {
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}`);
if (externalGameResponse.ok) {
@ -134,7 +147,7 @@ async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFet
for (const externalCategory of externalGame.categories) {
const category = await createCategory(locals, externalCategory, externalGame.external_id);
categories.push({
id: category.id
id: category.id,
});
}
for (const externalMechanic of externalGame.mechanics) {
@ -151,7 +164,7 @@ async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFet
if (externalExpansion?.inbound === true) {
createExpansion(locals, externalExpansion);
} 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 { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server';
import db from '$lib/drizzle';
import db from '../../../db';
import { lucia } from '$lib/server/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';
export const load: PageServerLoad = async (event) => {

View file

@ -1,21 +1,21 @@
import {fail, error, type Actions} from '@sveltejs/kit';
import {Argon2id} from 'oslo/password';
import {eq} from 'drizzle-orm';
import {zod} from 'sveltekit-superforms/adapters';
import {setError, superValidate} from 'sveltekit-superforms/server';
import {redirect} from 'sveltekit-flash-message/server';
import {RateLimiter} from 'sveltekit-rate-limiter/server';
import type {PageServerLoad} from './$types';
import {lucia} from '$lib/server/auth';
import {signUpSchema} from '$lib/validations/auth';
import {add_user_to_role} from '$server/roles';
import db from '$lib/drizzle';
import {collections, users, wishlists} from '../../../schema';
import {createId as cuid2} from '@paralleldrive/cuid2';
import { fail, error, type Actions } from '@sveltejs/kit';
import { Argon2id } from 'oslo/password';
import { eq } from 'drizzle-orm';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { RateLimiter } from 'sveltekit-rate-limiter/server';
import type { PageServerLoad } from './$types';
import { lucia } from '$lib/server/auth';
import { signUpSchema } from '$lib/validations/auth';
import { add_user_to_role } from '$server/roles';
import db from '../../../db';
import { collections, users, wishlists } from '$db/schema';
import { createId as cuid2 } from '@paralleldrive/cuid2';
const limiter = new RateLimiter({
// A rate is defined by [number, unit]
IPUA: [5, 'm']
IPUA: [5, 'm'],
});
const signUpDefaults = {
@ -25,7 +25,7 @@ const signUpDefaults = {
username: '',
password: '',
confirm_password: '',
terms: true
terms: true,
};
export const load: PageServerLoad = async (event) => {
@ -37,14 +37,14 @@ export const load: PageServerLoad = async (event) => {
// );
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);
}
return {
form: await superValidate(zod(signUpSchema), {
defaults: signUpDefaults
})
defaults: signUpDefaults,
}),
};
};
@ -59,7 +59,7 @@ export const actions: Actions = {
form.data.password = '';
form.data.confirm_password = '';
return fail(400, {
form
form,
});
}
@ -69,7 +69,7 @@ export const actions: Actions = {
console.log('Check if user already exists');
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) {
@ -81,41 +81,41 @@ export const actions: Actions = {
const hashedPassword = await new Argon2id().hash(form.data.password);
const user = await db
.insert(users)
.values({
username: form.data.username,
hashed_password: hashedPassword,
email: form.data.email,
first_name: form.data.firstName ?? '',
last_name: form.data.lastName ?? '',
verified: false,
receive_email: false,
theme: 'system',
two_factor_secret: '',
two_factor_enabled: false
})
.returning();
.insert(users)
.values({
username: form.data.username,
hashed_password: hashedPassword,
email: form.data.email,
first_name: form.data.firstName ?? '',
last_name: form.data.lastName ?? '',
verified: false,
receive_email: false,
theme: 'system',
two_factor_secret: '',
two_factor_enabled: false,
})
.returning();
console.log('signup user', user);
if (!user || user.length === 0) {
return fail(400, {
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);
await db.insert(collections).values({
user_id: user[0].id
user_id: user[0].id,
});
await db.insert(wishlists).values({
user_id: user[0].id
user_id: user[0].id,
});
try {
session = await lucia.createSession(user[0].id, {
ip_country: event.locals.ip,
ip_address: event.locals.country
ip_address: event.locals.country,
});
sessionCookie = lucia.createSessionCookie(session.id);
} catch (e: any) {
@ -126,7 +126,7 @@ export const actions: Actions = {
console.log(e);
const message = {
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.confirm_password = '';
@ -135,11 +135,11 @@ export const actions: Actions = {
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes
...sessionCookie.attributes,
});
redirect(302, '/');
// const message = { type: 'success', message: 'Signed Up!' } as const;
// 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 { eq } from 'drizzle-orm';
import { users } from '../../../../schema.js';
import { users } from '$db/schema';
import { createPasswordResetToken } from '$lib/server/auth-utils.js';
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({
where: eq(users.email, email)
where: eq(users.email, email),
});
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);
@ -27,6 +29,6 @@ export async function POST({ locals, request }) {
console.log('Verification link: ' + verificationLink);
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 { password_reset_tokens, users } from '../../../../../schema.js';
import { password_reset_tokens, users } from '$db/schema';
import { isWithinExpirationDate } from 'oslo';
import { lucia } from '$lib/server/auth.js';
import { Argon2id } from 'oslo/password';
@ -10,34 +10,31 @@ export async function POST({ request, params }) {
if (typeof password !== 'string' || password.length < 8) {
return new Response(null, {
status: 400
status: 400,
});
}
const verificationToken = params.token;
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) {
await db.delete(password_reset_tokens).where(eq(password_reset_tokens.id, verificationToken));
return new Response(null, {
status: 400
status: 400,
});
}
if (!token?.expires_at || !isWithinExpirationDate(token.expires_at)) {
return new Response(null, {
status: 400
status: 400,
});
}
await lucia.invalidateUserSessions(token.user_id);
const hashPassword = await new Argon2id().hash(password);
await db
.update(users)
.set({ hashed_password: hashPassword })
.where(eq(users.id, token.user_id));
await db.update(users).set({ hashed_password: hashPassword }).where(eq(users.id, token.user_id));
const session = await lucia.createSession(token.user_id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
@ -45,8 +42,8 @@ export async function POST({ request, params }) {
return new Response(null, {
status: 302,
headers: {
Location: "/",
"Set-Cookie": sessionCookie.serialize()
}
Location: '/',
'Set-Cookie': sessionCookie.serialize(),
},
});
}
}

View file

@ -1,7 +1,7 @@
import { error, json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js';
import { collection_items, users } from '../../../../../schema.js';
import db from '../../../../../db';
import { collection_items, users } from '$db/schema';
// Search a user's collection
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({
where: eq(users.id, locals?.user?.id)
where: eq(users.id, locals?.user?.id),
});
console.log('collection', collection);
@ -37,12 +37,13 @@ export async function GET({ url, locals, params }) {
columns: {
id: true,
name: true,
thumb_url: true
thumb_url: true,
},
}
},
},
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') {
return asc(dbSort);
} else {
@ -50,7 +51,7 @@ export async function GET({ url, locals, params }) {
}
},
offset: skip,
limit
limit,
});
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 { asc, count } from 'drizzle-orm';
import { games, type Games } from '../../../../schema.js';
import { games, type Games } from '$db/schema';
export const GET = async ({ url }) => {
const searchParams = Object.fromEntries(url.searchParams);
@ -14,12 +14,13 @@ export const GET = async ({ url }) => {
try {
const totalGames = await db
.select({
value: count(games.id)
value: count(games.id),
})
.from(games);
const numberOfGames = totalGames[0].value || 0;
const randomIndex = Math.floor(Math.random() * numberOfGames);
const randomGames: Games[] = await db.select()
const randomGames: Games[] = await db
.select()
.from(games)
.orderBy(asc(games.id))
.limit(limit)
@ -29,4 +30,4 @@ export const GET = async ({ url }) => {
console.error(e);
throw error(500, { message: 'Something went wrong' });
}
}
};

View file

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

View file

@ -1,10 +1,10 @@
import { createPublisher } from '$lib/utils/db/publisherUtils.js';
import type { Publishers } from '../../../schema.js';
import type { Publishers } from '$db/schema';
type PublisherCreate = {
publisher: Publishers;
externalId: string;
}
};
export async function POST({ request, locals }) {
const data: PublisherCreate = await request.json();
@ -15,7 +15,7 @@ export async function POST({ request, locals }) {
} catch (e) {
console.error(e);
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 type { Publishers } from '../../../../schema.js';
import type { Publishers } from '$db/schema';
export async function GET({ locals, params }) {
try {
@ -7,7 +7,7 @@ export async function GET({ locals, params }) {
} catch (e) {
console.error(e);
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 publisherId = params.id;
return await updatePublisher(locals, data, publisherId);
}
}

View file

@ -1,7 +1,7 @@
import { error, json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import db from '$lib/drizzle.js';
import { wishlist_items, wishlists } from '../../../../../schema.js';
import db from '../../../../../db';
import { wishlist_items, wishlists } from '$db/schema';
// Search a user's collection
export async function GET({ url, locals, params }) {
@ -16,12 +16,12 @@ export async function GET({ url, locals, params }) {
if (!locals.user) {
return new Response(null, {
status: 401
status: 401,
});
}
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);
@ -38,9 +38,9 @@ export async function GET({ url, locals, params }) {
columns: {
id: true,
name: true,
thumb_url: true
}
}
thumb_url: true,
},
},
},
orderBy: (wishlist_items, { asc, desc }) => {
const dbSort = wishlist_items.created_at;
@ -51,7 +51,7 @@ export async function GET({ url, locals, params }) {
}
},
offset: skip,
limit
limit,
});
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 db from '$lib/drizzle';
import { roles, user_roles } from '../schema';
import db from '../db';
import { roles, user_roles } from '$db/schema';
export async function add_user_to_role(user_id: string, role_name: string, primary = false) {
// Find the role by its name
const role = await db.query.roles.findFirst({
where: eq(roles.name, role_name)
where: eq(roles.name, role_name),
});
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({
user_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 { users, type Users } from '../schema';
import { users, type Users } from '$db/schema';
import { add_user_to_role } from './roles';
export function create_user(user: Users) {
return db.insert(users).values({
username: user.username
username: user.username,
});
}
export async function find_or_create_user(user: Users) {
const existing_user = await db.query.users.findFirst({
where: eq(users.username, user.username)
where: eq(users.username, user.username),
});
if (existing_user) {
return existing_user;
@ -30,12 +30,12 @@ export async function find_user_with_roles(user_id: string) {
with: {
role: {
select: {
name: true
}
}
}
}
}
name: true,
},
},
},
},
},
});
if (!user_with_roles) {
throw new Error('User not found');
@ -43,6 +43,6 @@ export async function find_user_with_roles(user_id: string) {
return {
...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: {
toggleKeyCombo: 'control-alt-shift',
showToggleButton: 'always',
toggleButtonPos: 'bottom-right'
}
toggleButtonPos: 'bottom-right',
},
},
kit: {
adapter: adapter(),
@ -21,19 +21,20 @@ const config = {
$assets: './src/assets',
$components: './src/components',
'$components/*': 'src/lib/components/*',
$db: './src/db',
$server: './src/server',
$lib: './src/lib',
$state: './src/state',
$styles: './src/styles',
$themes: './src/themes'
}
$themes: './src/themes',
},
},
shadcn: {
componentPath: './src/lib/components/ui'
componentPath: './src/lib/components/ui',
},
compilerOptions: {
enableSourcemap: true,
}
enableSourcemap: true,
},
};
export default config;