mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Fixing loading the wishlist and collection on the main landing page and getting your wishlist by id.
This commit is contained in:
parent
940b485273
commit
ab4c019406
30 changed files with 684 additions and 458 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
"indentStyle": "tab",
|
"indentStyle": "tab",
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
"lineEnding": "lf",
|
"lineEnding": "lf",
|
||||||
"lineWidth": 100,
|
"lineWidth": 150,
|
||||||
"attributePosition": "auto",
|
"attributePosition": "auto",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"**/.DS_Store",
|
"**/.DS_Store",
|
||||||
|
|
@ -34,6 +34,9 @@
|
||||||
"bracketSameLine": false,
|
"bracketSameLine": false,
|
||||||
"quoteStyle": "single",
|
"quoteStyle": "single",
|
||||||
"attributePosition": "auto"
|
"attributePosition": "auto"
|
||||||
|
},
|
||||||
|
"parser": {
|
||||||
|
"unsafeParameterDecoratorsEnabled": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -30,10 +30,10 @@
|
||||||
"@playwright/test": "^1.46.1",
|
"@playwright/test": "^1.46.1",
|
||||||
"@sveltejs/adapter-auto": "^3.2.4",
|
"@sveltejs/adapter-auto": "^3.2.4",
|
||||||
"@sveltejs/enhanced-img": "^0.3.3",
|
"@sveltejs/enhanced-img": "^0.3.3",
|
||||||
"@sveltejs/kit": "^2.5.22",
|
"@sveltejs/kit": "^2.5.24",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/node": "^20.16.0",
|
"@types/node": "^20.16.1",
|
||||||
"@types/pg": "^8.11.6",
|
"@types/pg": "^8.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
"satori": "^0.10.14",
|
"satori": "^0.10.14",
|
||||||
"satori-html": "^0.3.2",
|
"satori-html": "^0.3.2",
|
||||||
"svelte": "5.0.0-next.175",
|
"svelte": "5.0.0-next.175",
|
||||||
"svelte-check": "^3.8.5",
|
"svelte-check": "^3.8.6",
|
||||||
"svelte-headless-table": "^0.18.2",
|
"svelte-headless-table": "^0.18.2",
|
||||||
"svelte-meta-tags": "^3.1.3",
|
"svelte-meta-tags": "^3.1.3",
|
||||||
"svelte-preprocess": "^6.0.2",
|
"svelte-preprocess": "^6.0.2",
|
||||||
|
|
@ -70,13 +70,15 @@
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
"tsx": "^4.17.0",
|
"tsx": "^4.17.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.4.1",
|
"vite": "^5.4.2",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/fira-mono": "^5.0.14",
|
"@fontsource/fira-mono": "^5.0.14",
|
||||||
|
"@hono/swagger-ui": "^0.4.0",
|
||||||
|
"@hono/zod-openapi": "^0.15.3",
|
||||||
"@hono/zod-validator": "^0.2.2",
|
"@hono/zod-validator": "^0.2.2",
|
||||||
"@iconify-icons/line-md": "^1.2.30",
|
"@iconify-icons/line-md": "^1.2.30",
|
||||||
"@iconify-icons/mdi": "^1.2.48",
|
"@iconify-icons/mdi": "^1.2.48",
|
||||||
|
|
@ -93,17 +95,18 @@
|
||||||
"arctic": "^1.9.2",
|
"arctic": "^1.9.2",
|
||||||
"bits-ui": "^0.21.13",
|
"bits-ui": "^0.21.13",
|
||||||
"boardgamegeekclient": "^1.9.1",
|
"boardgamegeekclient": "^1.9.1",
|
||||||
"bullmq": "^5.12.9",
|
"bullmq": "^5.12.10",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^0.6.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv-expand": "^11.0.6",
|
"dotenv-expand": "^11.0.6",
|
||||||
"drizzle-orm": "^0.32.2",
|
"drizzle-orm": "^0.32.2",
|
||||||
|
"drizzle-zod": "^0.5.1",
|
||||||
"feather-icons": "^4.29.2",
|
"feather-icons": "^4.29.2",
|
||||||
"formsnap": "^1.0.1",
|
"formsnap": "^1.0.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"hono": "^4.5.6",
|
"hono": "^4.5.8",
|
||||||
"hono-rate-limiter": "^0.4.0",
|
"hono-rate-limiter": "^0.4.0",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"iconify-icon": "^2.1.0",
|
"iconify-icon": "^2.1.0",
|
||||||
|
|
|
||||||
463
pnpm-lock.yaml
463
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
9
src/app.d.ts
vendored
9
src/app.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
import { ApiClient } from './lib/server/api';
|
|
||||||
import type { User } from 'lucia';
|
import type { User } from 'lucia';
|
||||||
import { parseApiResponse } from '$lib/utils/api';
|
import type { ApiClient } from '$lib/server/api';
|
||||||
|
import type { parseApiResponse } from '$lib/utils/api';
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
|
@ -41,9 +41,12 @@ declare global {
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
startViewTransition: (callback: any) => void; // Add your custom property/method here
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
startViewTransition: (callback: never) => void; // Add your custom property/method here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS IS IMPORTANT!!!
|
// THIS IS IMPORTANT!!!
|
||||||
|
// biome-ignore lint/complexity/noUselessEmptyExport: <explanation>
|
||||||
|
// biome-ignore lint/style/useExportType: <explanation>
|
||||||
export {};
|
export {};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const apiClient: Handle = async ({ event, resolve }) => {
|
||||||
/* ----------------------------- Auth functions ----------------------------- */
|
/* ----------------------------- Auth functions ----------------------------- */
|
||||||
async function getAuthedUser() {
|
async function getAuthedUser() {
|
||||||
const { data } = await api.user.$get().then(parseApiResponse)
|
const { data } = await api.user.$get().then(parseApiResponse)
|
||||||
return data && data.user;
|
return data?.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAuthedUserOrThrow() {
|
async function getAuthedUserOrThrow() {
|
||||||
|
|
|
||||||
5
src/lib/dtos/id-params.dto.ts
Normal file
5
src/lib/dtos/id-params.dto.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const IdParamsDto = z.object({
|
||||||
|
id: z.trim().number(),
|
||||||
|
});
|
||||||
31
src/lib/server/api/controllers/collection.controller.ts
Normal file
31
src/lib/server/api/controllers/collection.controller.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware";
|
||||||
|
import type { HonoTypes } from '../types';
|
||||||
|
import type { Controller } from '../interfaces/controller.interface';
|
||||||
|
import {CollectionsService} from "$lib/server/api/services/collections.service";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CollectionController implements Controller {
|
||||||
|
controller = new Hono<HonoTypes>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(CollectionsService) private readonly collectionsService: CollectionsService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
routes() {
|
||||||
|
return this.controller
|
||||||
|
.get('/', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user;
|
||||||
|
const collections = await this.collectionsService.findAllByUserId(user.id);
|
||||||
|
console.log('collections service', collections)
|
||||||
|
return c.json({ collections });
|
||||||
|
})
|
||||||
|
.get('/:cuid', requireAuth, async (c) => {
|
||||||
|
const cuid = c.req.param('cuid');
|
||||||
|
const user = await this.collectionsService.findOneByCuid(cuid);
|
||||||
|
return c.json({ user });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,48 +1,54 @@
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono'
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe'
|
||||||
import { setCookie } from 'hono/cookie';
|
import { setCookie } from 'hono/cookie'
|
||||||
import { zValidator } from '@hono/zod-validator';
|
import { zValidator } from '@hono/zod-validator'
|
||||||
import type { HonoTypes } from '../types';
|
import type { HonoTypes } from '../types'
|
||||||
import { requireAuth } from "../middleware/auth.middleware";
|
import { requireAuth } from '../middleware/auth.middleware'
|
||||||
import type { Controller } from '../interfaces/controller.interface';
|
import type { Controller } from '$lib/server/api/interfaces/controller.interface'
|
||||||
import {IamService} from "$lib/server/api/services/iam.service";
|
import { IamService } from '$lib/server/api/services/iam.service'
|
||||||
import {LuciaProvider} from "$lib/server/api/providers";
|
import { LuciaProvider } from '$lib/server/api/providers'
|
||||||
import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware";
|
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
|
||||||
import {updateProfileDto} from "$lib/dtos/update-profile.dto";
|
import { updateProfileDto } from '$lib/dtos/update-profile.dto'
|
||||||
import {updateEmailDto} from "$lib/dtos/update-email.dto";
|
import { updateEmailDto } from '$lib/dtos/update-email.dto'
|
||||||
|
import { StatusCodes } from '$lib/constants/status-codes'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IamController implements Controller {
|
export class IamController implements Controller {
|
||||||
controller = new Hono<HonoTypes>();
|
controller = new Hono<HonoTypes>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(IamService) private readonly iamService: IamService,
|
@inject(IamService) private readonly iamService: IamService,
|
||||||
@inject(LuciaProvider) private lucia: LuciaProvider
|
@inject(LuciaProvider) private lucia: LuciaProvider,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.controller
|
return this.controller
|
||||||
.get('/me', requireAuth, async (c) => {
|
.get('/', requireAuth, async (c) => {
|
||||||
const user = c.var.user;
|
const user = c.var.user
|
||||||
return c.json({ user });
|
return c.json({ user })
|
||||||
})
|
})
|
||||||
.post('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
.put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => {
|
||||||
const user = c.var.user;
|
const user = c.var.user
|
||||||
console.log('user id', user.id);
|
const { firstName, lastName, username } = c.req.valid('json')
|
||||||
const { firstName, lastName, username } = c.req.valid('json');
|
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username })
|
||||||
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
|
if (!updatedUser) {
|
||||||
return c.json({ status: 'success' });
|
return c.json("Username already in use", StatusCodes.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return c.json({ user: updatedUser }, StatusCodes.OK)
|
||||||
})
|
})
|
||||||
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
.post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||||
const user = c.var.user;
|
const user = c.var.user
|
||||||
const { email } = c.req.valid('json');
|
const { email } = c.req.valid('json')
|
||||||
await this.iamService.updateEmail(user.id, { email });
|
const updatedUser = await this.iamService.updateEmail(user.id, { email })
|
||||||
return c.json({ status: 'success' });
|
if (!updatedUser) {
|
||||||
|
return c.json("Email already in use", StatusCodes.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return c.json({ user: updatedUser }, StatusCodes.OK)
|
||||||
})
|
})
|
||||||
.post('/logout', requireAuth, async (c) => {
|
.post('/logout', requireAuth, async (c) => {
|
||||||
const sessionId = c.var.session.id;
|
const sessionId = c.var.session.id
|
||||||
await this.iamService.logout(sessionId);
|
await this.iamService.logout(sessionId)
|
||||||
const sessionCookie = this.lucia.createBlankSessionCookie();
|
const sessionCookie = this.lucia.createBlankSessionCookie()
|
||||||
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
setCookie(c, sessionCookie.name, sessionCookie.value, {
|
||||||
path: sessionCookie.attributes.path,
|
path: sessionCookie.attributes.path,
|
||||||
maxAge: sessionCookie.attributes.maxAge,
|
maxAge: sessionCookie.attributes.maxAge,
|
||||||
|
|
@ -50,9 +56,9 @@ export class IamController implements Controller {
|
||||||
sameSite: sessionCookie.attributes.sameSite as any,
|
sameSite: sessionCookie.attributes.sameSite as any,
|
||||||
secure: sessionCookie.attributes.secure,
|
secure: sessionCookie.attributes.secure,
|
||||||
httpOnly: sessionCookie.attributes.httpOnly,
|
httpOnly: sessionCookie.attributes.httpOnly,
|
||||||
expires: sessionCookie.attributes.expires
|
expires: sessionCookie.attributes.expires,
|
||||||
});
|
})
|
||||||
return c.json({ status: 'success' });
|
return c.json({ status: 'success' })
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { requireAuth } from "../middleware/auth.middleware";
|
import { requireAuth } from "../middleware/auth.middleware";
|
||||||
import type { HonoTypes } from '../types';
|
import type { HonoTypes } from '../types';
|
||||||
import type { Controller } from '../interfaces/controller.interface';
|
import type { Controller } from '../interfaces/controller.interface';
|
||||||
|
import {UsersService} from "$lib/server/api/services/users.service";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserController implements Controller {
|
export class UserController implements Controller {
|
||||||
controller = new Hono<HonoTypes>();
|
controller = new Hono<HonoTypes>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@inject(UsersService) private readonly usersService: UsersService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
|
|
@ -20,12 +22,12 @@ export class UserController implements Controller {
|
||||||
})
|
})
|
||||||
.get('/:id', requireAuth, async (c) => {
|
.get('/:id', requireAuth, async (c) => {
|
||||||
const id = c.req.param('id');
|
const id = c.req.param('id');
|
||||||
const user = c.var.user;
|
const user = await this.usersService.findOneById(id);
|
||||||
return c.json({ user });
|
return c.json({ user });
|
||||||
})
|
})
|
||||||
.get('/username/:userName', requireAuth, async (c) => {
|
.get('/username/:userName', requireAuth, async (c) => {
|
||||||
const userName = c.req.param('userName');
|
const userName = c.req.param('userName');
|
||||||
const user = c.var.user;
|
const user = await this.usersService.findOneByUsername(userName);
|
||||||
return c.json({ user });
|
return c.json({ user });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/lib/server/api/controllers/wishlist.controller.ts
Normal file
31
src/lib/server/api/controllers/wishlist.controller.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { inject, injectable } from 'tsyringe';
|
||||||
|
import { requireAuth } from "../middleware/auth.middleware";
|
||||||
|
import type { HonoTypes } from '../types';
|
||||||
|
import type { Controller } from '../interfaces/controller.interface';
|
||||||
|
import {WishlistsService} from "$lib/server/api/services/wishlists.service";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class WishlistController implements Controller {
|
||||||
|
controller = new Hono<HonoTypes>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(WishlistsService) private readonly wishlistsService: WishlistsService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
routes() {
|
||||||
|
return this.controller
|
||||||
|
.get('/', requireAuth, async (c) => {
|
||||||
|
const user = c.var.user;
|
||||||
|
const wishlists = await this.wishlistsService.findAllByUserId(user.id);
|
||||||
|
return c.json({ wishlists });
|
||||||
|
})
|
||||||
|
.get('/:cuid', requireAuth, async (c) => {
|
||||||
|
const cuid = c.req.param('cuid')
|
||||||
|
console.log(cuid)
|
||||||
|
const wishlist = await this.wishlistsService.findOneByCuid(cuid)
|
||||||
|
return c.json({ wishlist });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,8 @@ import { IamController } from './controllers/iam.controller';
|
||||||
import { LoginController } from './controllers/login.controller';
|
import { LoginController } from './controllers/login.controller';
|
||||||
import {UserController} from "$lib/server/api/controllers/user.controller";
|
import {UserController} from "$lib/server/api/controllers/user.controller";
|
||||||
import {SignupController} from "$lib/server/api/controllers/signup.controller";
|
import {SignupController} from "$lib/server/api/controllers/signup.controller";
|
||||||
|
import {WishlistController} from "$lib/server/api/controllers/wishlist.controller";
|
||||||
|
import {CollectionController} from "$lib/server/api/controllers/collection.controller";
|
||||||
|
|
||||||
/* ----------------------------------- Api ---------------------------------- */
|
/* ----------------------------------- Api ---------------------------------- */
|
||||||
const app = new Hono().basePath('/api');
|
const app = new Hono().basePath('/api');
|
||||||
|
|
@ -40,6 +42,8 @@ const routes = app
|
||||||
.route('/user', container.resolve(UserController).routes())
|
.route('/user', container.resolve(UserController).routes())
|
||||||
.route('/login', container.resolve(LoginController).routes())
|
.route('/login', container.resolve(LoginController).routes())
|
||||||
.route('/signup', container.resolve(SignupController).routes())
|
.route('/signup', container.resolve(SignupController).routes())
|
||||||
|
.route('/wishlists', container.resolve(WishlistController).routes())
|
||||||
|
.route('/collections', container.resolve(CollectionController).routes())
|
||||||
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ export const lucia = new Lucia(adapter, {
|
||||||
// ...attributes,
|
// ...attributes,
|
||||||
username: attributes.username,
|
username: attributes.username,
|
||||||
email: attributes.email,
|
email: attributes.email,
|
||||||
firstName: attributes.firstName,
|
firstName: attributes.first_name,
|
||||||
lastName: attributes.lastName,
|
lastName: attributes.last_name,
|
||||||
theme: attributes.theme,
|
theme: attributes.theme,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -54,8 +54,8 @@ declare module 'lucia' {
|
||||||
interface DatabaseUserAttributes {
|
interface DatabaseUserAttributes {
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
first_name: string;
|
||||||
lastName: string;
|
last_name: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||||
|
import type { z } from 'zod'
|
||||||
|
import { collections } from '$lib/server/api/infrastructure/database/tables'
|
||||||
|
|
||||||
|
export const InsertCollectionSchema = createInsertSchema(collections, {
|
||||||
|
name: (schema) => schema.name.trim()
|
||||||
|
.min(3, { message: 'Must be at least 3 characters' })
|
||||||
|
.max(64, { message: 'Must be less than 64 characters' }).optional(),
|
||||||
|
}).omit({
|
||||||
|
id: true,
|
||||||
|
cuid: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type InsertCollectionSchema = z.infer<typeof InsertCollectionSchema>
|
||||||
|
|
||||||
|
export const SelectCollectionSchema = createSelectSchema(collections).omit({
|
||||||
|
id: true,
|
||||||
|
user_id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SelectUserSchema = z.infer<typeof SelectCollectionSchema>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
|
||||||
|
import type { z } from 'zod'
|
||||||
|
import { usersTable } from '$lib/server/api/infrastructure/database/tables'
|
||||||
|
|
||||||
|
export const InsertUserSchema = createInsertSchema(usersTable, {
|
||||||
|
email: (schema) => schema.email.max(64).email().optional(),
|
||||||
|
username: (schema) =>
|
||||||
|
schema.username.min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }).optional(),
|
||||||
|
first_name: (schema) =>
|
||||||
|
schema.first_name.trim().min(3, { message: 'Must be at least 3 characters' }).max(64, { message: 'Must be less than 64 characters' }).optional(),
|
||||||
|
last_name: (schema) =>
|
||||||
|
schema.last_name.trim().min(3, { message: 'Must be at least 3 characters' }).max(64, { message: 'Must be less than 64 characters' }).optional(),
|
||||||
|
}).omit({
|
||||||
|
id: true,
|
||||||
|
cuid: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type InsertUserSchema = z.infer<typeof InsertUserSchema>
|
||||||
|
|
||||||
|
export const SelectUserSchema = createSelectSchema(usersTable)
|
||||||
|
|
||||||
|
export type SelectUserSchema = z.infer<typeof SelectUserSchema>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
import { boolean, pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
|
||||||
import { type InferSelectModel, relations } from 'drizzle-orm';
|
import { type InferSelectModel, relations } from 'drizzle-orm';
|
||||||
|
import { createId as cuid2 } from '@paralleldrive/cuid2';
|
||||||
import { timestamps } from '../utils';
|
import { timestamps } from '../utils';
|
||||||
import {user_roles} from './userRoles';
|
import {user_roles} from './userRoles';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,36 @@ export class CollectionsRepository {
|
||||||
|
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.db.query.collections.findFirst({
|
return this.db.query.collections.findFirst({
|
||||||
where: eq(collections.id, id)
|
where: eq(collections.id, id),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneByCuid(cuid: string) {
|
||||||
|
return this.db.query.collections.findFirst({
|
||||||
|
where: eq(collections.cuid, cuid),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUserId(userId: string) {
|
async findOneByUserId(userId: string) {
|
||||||
return this.db.query.collections.findFirst({
|
return this.db.query.collections.findFirst({
|
||||||
|
where: eq(collections.user_id, userId),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByUserId(userId: string) {
|
||||||
|
return this.db.query.collections.findMany({
|
||||||
where: eq(collections.user_id, userId)
|
where: eq(collections.user_id, userId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,19 +17,41 @@ export class WishlistsRepository {
|
||||||
|
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.db.query.wishlists.findFirst({
|
return this.db.query.wishlists.findFirst({
|
||||||
where: eq(wishlists.id, id)
|
where: eq(wishlists.id, id),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneByCuid(cuid: string) {
|
||||||
|
return this.db.query.wishlists.findFirst({
|
||||||
|
where: eq(wishlists.cuid, cuid),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUserId(userId: string) {
|
async findOneByUserId(userId: string) {
|
||||||
return this.db.query.wishlists.findFirst({
|
return this.db.query.wishlists.findFirst({
|
||||||
where: eq(wishlists.user_id, userId)
|
where: eq(wishlists.user_id, userId),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllByUserId(userId: string) {
|
async findAllByUserId(userId: string) {
|
||||||
return this.db.query.wishlists.findMany({
|
return this.db.query.wishlists.findMany({
|
||||||
where: eq(wishlists.user_id, userId)
|
where: eq(wishlists.user_id, userId),
|
||||||
|
columns: {
|
||||||
|
cuid: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,22 @@ export class CollectionsService {
|
||||||
@inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository
|
@inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
async findOneByUserId(userId: string) {
|
||||||
|
return this.collectionsRepository.findOneByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByUserId(userId: string) {
|
||||||
|
return this.collectionsRepository.findAllByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string) {
|
||||||
|
return this.collectionsRepository.findOneById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneByCuid(cuid: string) {
|
||||||
|
return this.collectionsRepository.findOneByCuid(cuid);
|
||||||
|
}
|
||||||
|
|
||||||
async createEmptyNoName(userId: string) {
|
async createEmptyNoName(userId: string) {
|
||||||
return this.createEmpty(userId, null);
|
return this.createEmpty(userId, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
import type { UpdateEmailDto } from "$lib/dtos/update-email.dto";
|
||||||
|
import type { UpdateProfileDto } from "$lib/dtos/update-profile.dto";
|
||||||
|
import { UsersService } from "$lib/server/api/services/users.service";
|
||||||
import { inject, injectable } from 'tsyringe';
|
import { inject, injectable } from 'tsyringe';
|
||||||
import { LuciaProvider } from '../providers/lucia.provider';
|
import { LuciaProvider } from '$lib/server/api/providers';
|
||||||
import {UsersService} from "$lib/server/api/services/users.service";
|
|
||||||
import type {UpdateProfileDto} from "$lib/dtos/update-profile.dto";
|
|
||||||
import type {UpdateEmailDto} from "$lib/dtos/update-email.dto";
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Service */
|
/* Service */
|
||||||
|
|
@ -55,8 +55,15 @@ export class IamService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEmail(userId: string, data: UpdateEmailDto) {
|
async updateEmail(userId: string, data: UpdateEmailDto) {
|
||||||
|
const { email } = data;
|
||||||
|
|
||||||
|
const existingUserEmail = await this.usersService.findOneByEmail(email);
|
||||||
|
if (existingUserEmail && existingUserEmail.id !== userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return this.usersService.updateUser(userId, {
|
return this.usersService.updateUser(userId, {
|
||||||
email: data.email
|
email,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ export class UsersService {
|
||||||
return this.usersRepository.findOneByUsername(username);
|
return this.usersRepository.findOneByUsername(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneByEmail(email: string) {
|
||||||
|
return this.usersRepository.findOneByEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
async findOneById(id: string) {
|
async findOneById(id: string) {
|
||||||
return this.usersRepository.findOneById(id);
|
return this.usersRepository.findOneById(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ export class WishlistsService {
|
||||||
return this.wishlistsRepository.findAllByUserId(userId);
|
return this.wishlistsRepository.findAllByUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string) {
|
||||||
|
return this.wishlistsRepository.findOneById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneByCuid(cuid: string) {
|
||||||
|
return this.wishlistsRepository.findOneByCuid(cuid);
|
||||||
|
}
|
||||||
|
|
||||||
async createEmptyNoName(userId: string) {
|
async createEmptyNoName(userId: string) {
|
||||||
return this.createEmpty(userId, null);
|
return this.createEmpty(userId, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export async function parseApiResponse<T>(response: ClientResponse<T>) {
|
||||||
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
|
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
|
||||||
return response.ok
|
return response.ok
|
||||||
? { data: null, error: null, response }
|
? { data: null, error: null, response }
|
||||||
: { data: null, error: 'An unknown error has occured', response };
|
: { data: null, error: 'An unknown error has occurred', response };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>
|
||||||
return z.preprocess(
|
return z.preprocess(
|
||||||
(value) =>
|
(value) =>
|
||||||
typeof value === 'string'
|
typeof value === 'string'
|
||||||
? parseInt(value, 10)
|
? Number.parseInt(value, 10)
|
||||||
: typeof value === 'number'
|
: typeof value === 'number'
|
||||||
? value
|
? value
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,19 @@ export const load: PageServerLoad = async (event) => {
|
||||||
if (!authedUser) {
|
if (!authedUser) {
|
||||||
throw redirect(302, '/login', notSignedInMessage, event);
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('authedUser', authedUser);
|
||||||
// if (userNotAuthenticated(user, session)) {
|
// if (userNotAuthenticated(user, session)) {
|
||||||
// redirect(302, '/login', notSignedInMessage, event);
|
// redirect(302, '/login', notSignedInMessage, event);
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// const dbUser = await db.query.usersTable.findFirst({
|
// const dbUser = await db.query.usersTable.findFirst({
|
||||||
// where: eq(usersTable.id, user!.id!),
|
// where: eq(usersTable.id, user!.id!),
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const profileForm = await superValidate(zod(profileSchema), {
|
const profileForm = await superValidate(zod(profileSchema), {
|
||||||
defaults: {
|
defaults: {
|
||||||
firstName: authedUser?.first_name ?? '',
|
firstName: authedUser?.firstName ?? '',
|
||||||
lastName: authedUser?.last_name ?? '',
|
lastName: authedUser?.lastName ?? '',
|
||||||
username: authedUser?.username ?? '',
|
username: authedUser?.username ?? '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -72,8 +73,11 @@ export const actions: Actions = {
|
||||||
|
|
||||||
const form = await superValidate(event, zod(updateProfileDto));
|
const form = await superValidate(event, zod(updateProfileDto));
|
||||||
|
|
||||||
const { error } = await locals.api.user.$post({ json: form.data }).then(locals.parseApiResponse);
|
const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parwseApiResponse);
|
||||||
if (error) return setError(form, 'username', error);
|
console.log('data from profile update', error);
|
||||||
|
if (error) {
|
||||||
|
return setError(form, 'username', error);
|
||||||
|
}
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
|
|
@ -81,36 +85,6 @@ export const actions: Actions = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('updating profile');
|
|
||||||
|
|
||||||
const user = event.locals.user;
|
|
||||||
const newUsername = form.data.username;
|
|
||||||
const existingUser = await db.query.usersTable.findFirst({
|
|
||||||
where: eq(usersTable.username, newUsername),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingUser && existingUser.id !== user.id) {
|
|
||||||
return setError(form, 'username', 'That username is already taken');
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.update(usersTable)
|
|
||||||
.set({
|
|
||||||
first_name: form.data.firstName,
|
|
||||||
last_name: form.data.lastName,
|
|
||||||
username: form.data.username,
|
|
||||||
})
|
|
||||||
.where(eq(usersTable.id, user.id));
|
|
||||||
} catch (e) {
|
|
||||||
// @ts-expect-error
|
|
||||||
if (e.message === `AUTH_INVALID_USER_ID`) {
|
|
||||||
// invalid user id
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
return setError(form, 'There was a problem updating your profile.');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('profile updated successfully');
|
console.log('profile updated successfully');
|
||||||
return message(form, { type: 'success', message: 'Profile updated successfully!' });
|
return message(form, { type: 'success', message: 'Profile updated successfully!' });
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,33 @@ import { zod } from 'sveltekit-superforms/adapters';
|
||||||
import { superValidate } from 'sveltekit-superforms/server';
|
import { superValidate } from 'sveltekit-superforms/server';
|
||||||
import { redirect } from 'sveltekit-flash-message/server';
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
|
import { modifyListGameSchema } from '$lib/validations/zod-schemas';
|
||||||
import db from '../../../../../db';
|
import { db } from '$lib/server/api/infrastructure/database';
|
||||||
import { notSignedInMessage } from '$lib/flashMessages.js';
|
import { notSignedInMessage } from '$lib/flashMessages.js';
|
||||||
import { games, wishlist_items, wishlists } from '$db/schema';
|
import { games, wishlist_items, wishlists } from '$lib/server/api/infrastructure/database/tables';
|
||||||
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
import { userNotAuthenticated } from '$lib/server/auth-utils';
|
||||||
|
|
||||||
export async function load(event) {
|
export async function load(event) {
|
||||||
const { params, locals } = event;
|
const { params, locals } = event;
|
||||||
const { user, session } = locals;
|
const { cuid } = params;
|
||||||
const { id } = params;
|
|
||||||
if (userNotAuthenticated(user, session)) {
|
const authedUser = await locals.getAuthedUser();
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
if (!authedUser) {
|
||||||
|
throw redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wishlist = await db.query.wishlists.findMany({
|
const { data, errors } = await locals.api.wishlists[':cuid'].$get({
|
||||||
where: and(eq(wishlists.user_id, user!.id!), eq(wishlists.cuid, id)),
|
param: { cuid }
|
||||||
});
|
}).then(locals.parseApiResponse);
|
||||||
|
// const wishlist = await db.query.wishlists.findMany({
|
||||||
|
// where: and(eq(wishlists.user_id, authedUser.id), eq(wishlists.cuid, cuid)),
|
||||||
|
// });
|
||||||
|
if (errors) {
|
||||||
|
return error(500, 'Failed to fetch wishlist');
|
||||||
|
}
|
||||||
|
console.log('data', data);
|
||||||
|
const { wishlist } = data;
|
||||||
|
console.log('wishlist', wishlist);
|
||||||
|
|
||||||
if (!wishlist) {
|
if (!wishlist) {
|
||||||
redirect(302, '/404');
|
redirect(302, '/404');
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { fail } from '@sveltejs/kit';
|
||||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
@ -43,37 +44,24 @@ export const load: PageServerLoad = async (event) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authedUser) {
|
if (authedUser) {
|
||||||
const dbUser = await db.query.usersTable.findFirst({
|
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse);
|
||||||
where: eq(usersTable.id, authedUser!.id!),
|
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse);
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Sending back user details');
|
if (wishlistsError || collectionsError) {
|
||||||
const userWishlists = await db.query.wishlists.findMany({
|
return fail(500, 'Failed to fetch wishlists or collections');
|
||||||
columns: {
|
}
|
||||||
cuid: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
where: eq(wishlists.user_id, authedUser!.id!),
|
|
||||||
});
|
|
||||||
const userCollection = await db.query.collections.findMany({
|
|
||||||
columns: {
|
|
||||||
cuid: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
where: eq(collections.user_id, authedUser!.id!),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Wishlists', userWishlists);
|
console.log('Wishlists', wishlistsData.wishlists);
|
||||||
console.log('Collections', userCollection);
|
console.log('Collections', collectionsData.collections);
|
||||||
return {
|
return {
|
||||||
metaTagsChild: metaTags,
|
metaTagsChild: metaTags,
|
||||||
user: {
|
user: {
|
||||||
firstName: dbUser?.first_name,
|
firstName: authedUser?.firstName,
|
||||||
lastName: dbUser?.last_name,
|
lastName: authedUser?.lastName,
|
||||||
username: dbUser?.username,
|
username: authedUser?.username,
|
||||||
},
|
},
|
||||||
wishlists: userWishlists,
|
wishlists: wishlistsData.wishlists,
|
||||||
collections: userCollection,
|
collections: collectionsData.collections,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
import { page } from '$app/stores';
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from '$lib/components/ui/button';
|
||||||
import Logo from "$lib/components/logo.svelte";
|
import Logo from '$lib/components/logo.svelte';
|
||||||
import Transition from '$lib/components/transition.svelte';
|
import Transition from '$lib/components/transition.svelte';
|
||||||
|
|
||||||
let { data, children } = $props();
|
let { data, children } = $props();
|
||||||
|
|
@ -15,21 +15,11 @@
|
||||||
Bored Game
|
Bored Game
|
||||||
</a>
|
</a>
|
||||||
<div class="auth-buttons">
|
<div class="auth-buttons">
|
||||||
{#if $page.url.pathname !== "/login"}
|
{#if $page.url.pathname !== '/login'}
|
||||||
<Button
|
<Button href="/login" variant="ghost">Login</Button>
|
||||||
href="/login"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if $page.url.pathname !== "/sign-up"}
|
{#if $page.url.pathname !== '/sign-up'}
|
||||||
<Button
|
<Button href="/sign-up" variant="ghost">Sign up</Button>
|
||||||
href="/sign-up"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
Sign up
|
|
||||||
</Button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-marketing">
|
<div class="auth-marketing">
|
||||||
|
|
@ -42,7 +32,8 @@
|
||||||
<div class="quote-wrapper">
|
<div class="quote-wrapper">
|
||||||
<blockquote class="quote">
|
<blockquote class="quote">
|
||||||
<p>
|
<p>
|
||||||
"How many games do I own? What was the last one I played? What haven't I played in a long time? If this sounds like you then Bored Game is your new best friend."
|
"How many games do I own? What was the last one I played? What haven't I played in a long
|
||||||
|
time? If this sounds like you then Bored Game is your new best friend."
|
||||||
</p>
|
</p>
|
||||||
<footer>Bradley</footer>
|
<footer>Bradley</footer>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
@ -63,7 +54,7 @@
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
@media (width >= 768px) {
|
@media (width >= 768px) {
|
||||||
display: grid
|
display: grid;
|
||||||
}
|
}
|
||||||
@media (width >= 1024px) {
|
@media (width >= 1024px) {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|
@ -135,7 +126,7 @@
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition-property: color, background-color, border-color,text-decoration-color, fill, stroke;
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
transition-duration: 300ms;
|
transition-duration: 300ms;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
|
|
@ -152,7 +143,6 @@
|
||||||
|
|
||||||
@media (width <= 768px) {
|
@media (width <= 768px) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width > 768px) {
|
@media (width > 768px) {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import { fail, type Actions } from '@sveltejs/kit';
|
import { fail, type Actions } from '@sveltejs/kit'
|
||||||
import { eq, or } from 'drizzle-orm';
|
import { eq, or } from 'drizzle-orm'
|
||||||
import { Argon2id } from 'oslo/password';
|
import { Argon2id } from 'oslo/password'
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
import { zod } from 'sveltekit-superforms/adapters'
|
||||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
import { setError, superValidate } from 'sveltekit-superforms/server'
|
||||||
import { redirect } from 'sveltekit-flash-message/server';
|
import { redirect } from 'sveltekit-flash-message/server'
|
||||||
import { db } from '../../../lib/server/api/infrastructure/database/index';
|
import { db } from '../../../lib/server/api/infrastructure/database/index'
|
||||||
import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia';
|
import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia'
|
||||||
import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables';
|
import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables'
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types'
|
||||||
import {signinUsernameDto} from "$lib/dtos/signin-username.dto";
|
import { signinUsernameDto } from '$lib/dtos/signin-username.dto'
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event
|
||||||
|
|
||||||
const authedUser = await locals.getAuthedUser();
|
const authedUser = await locals.getAuthedUser()
|
||||||
|
|
||||||
if (authedUser) {
|
if (authedUser) {
|
||||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
const message = { type: 'success', message: 'You are already signed in' } as const
|
||||||
throw redirect('/', message, event);
|
throw redirect('/', message, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (userFullyAuthenticated(user, session)) {
|
// if (userFullyAuthenticated(user, session)) {
|
||||||
|
|
@ -31,34 +31,34 @@ export const load: PageServerLoad = async (event) => {
|
||||||
// ...sessionCookie.attributes,
|
// ...sessionCookie.attributes,
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
const form = await superValidate(event, zod(signinUsernameDto));
|
const form = await superValidate(event, zod(signinUsernameDto))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => {
|
default: async (event) => {
|
||||||
const { locals } = event;
|
const { locals } = event
|
||||||
|
|
||||||
const authedUser = await locals.getAuthedUser();
|
const authedUser = await locals.getAuthedUser()
|
||||||
|
|
||||||
if (authedUser) {
|
if (authedUser) {
|
||||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
const message = { type: 'success', message: 'You are already signed in' } as const
|
||||||
throw redirect('/', message, event);
|
throw redirect('/', message, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = await superValidate(event, zod(signinUsernameDto));
|
const form = await superValidate(event, zod(signinUsernameDto))
|
||||||
|
|
||||||
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse);
|
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse)
|
||||||
if (error) return setError(form, 'username', error);
|
if (error) return setError(form, 'username', error)
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
form.data.password = '';
|
form.data.password = ''
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
form,
|
form,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// let session;
|
// let session;
|
||||||
|
|
@ -75,59 +75,59 @@ export const actions: Actions = {
|
||||||
// let twoFactorDetails;
|
// let twoFactorDetails;
|
||||||
//
|
//
|
||||||
try {
|
try {
|
||||||
// const password = form.data.password;
|
// const password = form.data.password;
|
||||||
// console.log('user', JSON.stringify(user, null, 2));
|
// console.log('user', JSON.stringify(user, null, 2));
|
||||||
//
|
//
|
||||||
// if (!user?.hashed_password) {
|
// if (!user?.hashed_password) {
|
||||||
// console.log('invalid username/password');
|
// console.log('invalid username/password');
|
||||||
// form.data.password = '';
|
// form.data.password = '';
|
||||||
// return setError(form, 'password', 'Your username or password is incorrect.');
|
// return setError(form, 'password', 'Your username or password is incorrect.');
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// const validPassword = await new Argon2id().verify(user.hashed_password, password);
|
// const validPassword = await new Argon2id().verify(user.hashed_password, password);
|
||||||
// if (!validPassword) {
|
// if (!validPassword) {
|
||||||
// console.log('invalid password');
|
// console.log('invalid password');
|
||||||
// form.data.password = '';
|
// form.data.password = '';
|
||||||
// return setError(form, 'password', 'Your username or password is incorrect.');
|
// return setError(form, 'password', 'Your username or password is incorrect.');
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// console.log('ip', locals.ip);
|
// console.log('ip', locals.ip);
|
||||||
// console.log('country', locals.country);
|
// console.log('country', locals.country);
|
||||||
//
|
//
|
||||||
// twoFactorDetails = await db.query.twoFactor.findFirst({
|
// twoFactorDetails = await db.query.twoFactor.findFirst({
|
||||||
// where: eq(twoFactor.userId, user?.id),
|
// where: eq(twoFactor.userId, user?.id),
|
||||||
// });
|
// });
|
||||||
//
|
//
|
||||||
// if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
|
// if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
|
||||||
// await db.update(twoFactor).set({
|
// await db.update(twoFactor).set({
|
||||||
// initiatedTime: new Date(),
|
// initiatedTime: new Date(),
|
||||||
// });
|
// });
|
||||||
//
|
//
|
||||||
// session = await lucia.createSession(user.id, {
|
// session = await lucia.createSession(user.id, {
|
||||||
// ip_country: locals.country,
|
// ip_country: locals.country,
|
||||||
// ip_address: locals.ip,
|
// ip_address: locals.ip,
|
||||||
// twoFactorAuthEnabled:
|
// twoFactorAuthEnabled:
|
||||||
// twoFactorDetails?.enabled &&
|
// twoFactorDetails?.enabled &&
|
||||||
// twoFactorDetails?.secret !== null &&
|
// twoFactorDetails?.secret !== null &&
|
||||||
// twoFactorDetails?.secret !== '',
|
// twoFactorDetails?.secret !== '',
|
||||||
// isTwoFactorAuthenticated: false,
|
// isTwoFactorAuthenticated: false,
|
||||||
// });
|
// });
|
||||||
// } else {
|
// } else {
|
||||||
// session = await lucia.createSession(user.id, {
|
// session = await lucia.createSession(user.id, {
|
||||||
// ip_country: locals.country,
|
// ip_country: locals.country,
|
||||||
// ip_address: locals.ip,
|
// ip_address: locals.ip,
|
||||||
// twoFactorAuthEnabled: false,
|
// twoFactorAuthEnabled: false,
|
||||||
// isTwoFactorAuthenticated: false,
|
// isTwoFactorAuthenticated: false,
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
// console.log('logging in session', session);
|
// console.log('logging in session', session);
|
||||||
// sessionCookie = lucia.createSessionCookie(session.id);
|
// sessionCookie = lucia.createSessionCookie(session.id);
|
||||||
// console.log('logging in session cookie', sessionCookie);
|
// console.log('logging in session cookie', sessionCookie);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: need to return error message to the client
|
// TODO: need to return error message to the client
|
||||||
console.error(e);
|
console.error(e)
|
||||||
form.data.password = '';
|
form.data.password = ''
|
||||||
return setError(form, '', 'Your username or password is incorrect.');
|
return setError(form, '', 'Your username or password is incorrect.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('setting session cookie', sessionCookie);
|
// console.log('setting session cookie', sessionCookie);
|
||||||
|
|
@ -136,8 +136,8 @@ export const actions: Actions = {
|
||||||
// ...sessionCookie.attributes,
|
// ...sessionCookie.attributes,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
form.data.username = '';
|
form.data.username = ''
|
||||||
form.data.password = '';
|
form.data.password = ''
|
||||||
|
|
||||||
// if (
|
// if (
|
||||||
// twoFactorDetails?.enabled &&
|
// twoFactorDetails?.enabled &&
|
||||||
|
|
@ -152,4 +152,4 @@ export const actions: Actions = {
|
||||||
// redirect(302, '/', message, event);
|
// redirect(302, '/', message, event);
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,25 +87,4 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.login {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 24rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
Loading…
Reference in a new issue