Fixing loading the wishlist and collection on the main landing page and getting your wishlist by id.

This commit is contained in:
Bradley Shellnut 2024-08-22 19:26:22 -07:00
parent 940b485273
commit ab4c019406
30 changed files with 684 additions and 458 deletions

View file

@ -6,7 +6,7 @@
"indentStyle": "tab",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"lineWidth": 150,
"attributePosition": "auto",
"ignore": [
"**/.DS_Store",
@ -34,6 +34,9 @@
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto"
},
"parser": {
"unsafeParameterDecoratorsEnabled": true
}
},
"overrides": [

View file

@ -30,10 +30,10 @@
"@playwright/test": "^1.46.1",
"@sveltejs/adapter-auto": "^3.2.4",
"@sveltejs/enhanced-img": "^0.3.3",
"@sveltejs/kit": "^2.5.22",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@sveltejs/kit": "^2.5.24",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20.16.0",
"@types/node": "^20.16.1",
"@types/pg": "^8.11.6",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
@ -57,7 +57,7 @@
"satori": "^0.10.14",
"satori-html": "^0.3.2",
"svelte": "5.0.0-next.175",
"svelte-check": "^3.8.5",
"svelte-check": "^3.8.6",
"svelte-headless-table": "^0.18.2",
"svelte-meta-tags": "^3.1.3",
"svelte-preprocess": "^6.0.2",
@ -70,13 +70,15 @@
"tslib": "^2.6.3",
"tsx": "^4.17.0",
"typescript": "^5.5.4",
"vite": "^5.4.1",
"vite": "^5.4.2",
"vitest": "^1.6.0",
"zod": "^3.23.8"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^5.0.14",
"@hono/swagger-ui": "^0.4.0",
"@hono/zod-openapi": "^0.15.3",
"@hono/zod-validator": "^0.2.2",
"@iconify-icons/line-md": "^1.2.30",
"@iconify-icons/mdi": "^1.2.48",
@ -93,17 +95,18 @@
"arctic": "^1.9.2",
"bits-ui": "^0.21.13",
"boardgamegeekclient": "^1.9.1",
"bullmq": "^5.12.9",
"bullmq": "^5.12.10",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cookie": "^0.6.0",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"drizzle-orm": "^0.32.2",
"drizzle-zod": "^0.5.1",
"feather-icons": "^4.29.2",
"formsnap": "^1.0.1",
"handlebars": "^4.7.8",
"hono": "^4.5.6",
"hono": "^4.5.8",
"hono-rate-limiter": "^0.4.0",
"html-entities": "^2.5.2",
"iconify-icon": "^2.1.0",

File diff suppressed because it is too large Load diff

9
src/app.d.ts vendored
View file

@ -1,6 +1,6 @@
import { ApiClient } from './lib/server/api';
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
// for information about these interfaces
@ -41,9 +41,12 @@ declare global {
interface Document {
// 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!!!
// biome-ignore lint/complexity/noUselessEmptyExport: <explanation>
// biome-ignore lint/style/useExportType: <explanation>
export {};

View file

@ -28,7 +28,7 @@ const apiClient: Handle = async ({ event, resolve }) => {
/* ----------------------------- Auth functions ----------------------------- */
async function getAuthedUser() {
const { data } = await api.user.$get().then(parseApiResponse)
return data && data.user;
return data?.user;
}
async function getAuthedUserOrThrow() {

View file

@ -0,0 +1,5 @@
import { z } from "zod";
export const IdParamsDto = z.object({
id: z.trim().number(),
});

View 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 });
});
}
}

View file

@ -1,48 +1,54 @@
import { Hono } from 'hono';
import { inject, injectable } from 'tsyringe';
import { setCookie } from 'hono/cookie';
import { zValidator } from '@hono/zod-validator';
import type { HonoTypes } from '../types';
import { requireAuth } from "../middleware/auth.middleware";
import type { Controller } from '../interfaces/controller.interface';
import {IamService} from "$lib/server/api/services/iam.service";
import {LuciaProvider} from "$lib/server/api/providers";
import {limiter} from "$lib/server/api/middleware/rate-limiter.middleware";
import {updateProfileDto} from "$lib/dtos/update-profile.dto";
import {updateEmailDto} from "$lib/dtos/update-email.dto";
import { Hono } from 'hono'
import { inject, injectable } from 'tsyringe'
import { setCookie } from 'hono/cookie'
import { zValidator } from '@hono/zod-validator'
import type { HonoTypes } from '../types'
import { requireAuth } from '../middleware/auth.middleware'
import type { Controller } from '$lib/server/api/interfaces/controller.interface'
import { IamService } from '$lib/server/api/services/iam.service'
import { LuciaProvider } from '$lib/server/api/providers'
import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware'
import { updateProfileDto } from '$lib/dtos/update-profile.dto'
import { updateEmailDto } from '$lib/dtos/update-email.dto'
import { StatusCodes } from '$lib/constants/status-codes'
@injectable()
export class IamController implements Controller {
controller = new Hono<HonoTypes>();
controller = new Hono<HonoTypes>()
constructor(
@inject(IamService) private readonly iamService: IamService,
@inject(LuciaProvider) private lucia: LuciaProvider
) { }
@inject(IamService) private readonly iamService: IamService,
@inject(LuciaProvider) private lucia: LuciaProvider,
) {}
routes() {
return this.controller
.get('/me', requireAuth, async (c) => {
const user = c.var.user;
return c.json({ user });
.get('/', requireAuth, async (c) => {
const user = c.var.user
return c.json({ user })
})
.post('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
const user = c.var.user;
console.log('user id', user.id);
const { firstName, lastName, username } = c.req.valid('json');
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username });
return c.json({ status: 'success' });
.put('/update/profile', requireAuth, zValidator('json', updateProfileDto), limiter({ limit: 30, minutes: 60 }), async (c) => {
const user = c.var.user
const { firstName, lastName, username } = c.req.valid('json')
const updatedUser = await this.iamService.updateProfile(user.id, { firstName, lastName, username })
if (!updatedUser) {
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) => {
const user = c.var.user;
const { email } = c.req.valid('json');
await this.iamService.updateEmail(user.id, { email });
return c.json({ status: 'success' });
const user = c.var.user
const { email } = c.req.valid('json')
const updatedUser = await this.iamService.updateEmail(user.id, { email })
if (!updatedUser) {
return c.json("Email already in use", StatusCodes.BAD_REQUEST);
}
return c.json({ user: updatedUser }, StatusCodes.OK)
})
.post('/logout', requireAuth, async (c) => {
const sessionId = c.var.session.id;
await this.iamService.logout(sessionId);
const sessionCookie = this.lucia.createBlankSessionCookie();
const sessionId = c.var.session.id
await this.iamService.logout(sessionId)
const sessionCookie = this.lucia.createBlankSessionCookie()
setCookie(c, sessionCookie.name, sessionCookie.value, {
path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes.maxAge,
@ -50,9 +56,9 @@ export class IamController implements Controller {
sameSite: sessionCookie.attributes.sameSite as any,
secure: sessionCookie.attributes.secure,
httpOnly: sessionCookie.attributes.httpOnly,
expires: sessionCookie.attributes.expires
});
return c.json({ status: 'success' });
});
expires: sessionCookie.attributes.expires,
})
return c.json({ status: 'success' })
})
}
}

View file

@ -1,15 +1,17 @@
import 'reflect-metadata';
import { Hono } from 'hono';
import { injectable } from 'tsyringe';
import { inject, injectable } from 'tsyringe';
import { requireAuth } from "../middleware/auth.middleware";
import type { HonoTypes } from '../types';
import type { Controller } from '../interfaces/controller.interface';
import {UsersService} from "$lib/server/api/services/users.service";
@injectable()
export class UserController implements Controller {
controller = new Hono<HonoTypes>();
constructor(
@inject(UsersService) private readonly usersService: UsersService
) { }
routes() {
@ -20,12 +22,12 @@ export class UserController implements Controller {
})
.get('/:id', requireAuth, async (c) => {
const id = c.req.param('id');
const user = c.var.user;
const user = await this.usersService.findOneById(id);
return c.json({ user });
})
.get('/username/:userName', requireAuth, async (c) => {
const userName = c.req.param('userName');
const user = c.var.user;
const user = await this.usersService.findOneByUsername(userName);
return c.json({ user });
});
}

View 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 });
});
}
}

View file

@ -10,6 +10,8 @@ import { IamController } from './controllers/iam.controller';
import { LoginController } from './controllers/login.controller';
import {UserController} from "$lib/server/api/controllers/user.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 ---------------------------------- */
const app = new Hono().basePath('/api');
@ -40,6 +42,8 @@ const routes = app
.route('/user', container.resolve(UserController).routes())
.route('/login', container.resolve(LoginController).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' }));
/* -------------------------------------------------------------------------- */

View file

@ -21,8 +21,8 @@ export const lucia = new Lucia(adapter, {
// ...attributes,
username: attributes.username,
email: attributes.email,
firstName: attributes.firstName,
lastName: attributes.lastName,
firstName: attributes.first_name,
lastName: attributes.last_name,
theme: attributes.theme,
};
},
@ -54,8 +54,8 @@ declare module 'lucia' {
interface DatabaseUserAttributes {
username: string;
email: string;
firstName: string;
lastName: string;
first_name: string;
last_name: string;
theme: string;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -1,6 +1,6 @@
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 { createId as cuid2 } from '@paralleldrive/cuid2';
import { timestamps } from '../utils';
import {user_roles} from './userRoles';

View file

@ -17,12 +17,36 @@ export class CollectionsRepository {
async findOneById(id: string) {
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) {
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)
})
}

View file

@ -17,19 +17,41 @@ export class WishlistsRepository {
async findOneById(id: string) {
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) {
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) {
return this.db.query.wishlists.findMany({
where: eq(wishlists.user_id, userId)
where: eq(wishlists.user_id, userId),
columns: {
cuid: true,
name: true
}
})
}

View file

@ -8,6 +8,22 @@ export class CollectionsService {
@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) {
return this.createEmpty(userId, null);
}

View file

@ -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 { LuciaProvider } from '../providers/lucia.provider';
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";
import { LuciaProvider } from '$lib/server/api/providers';
/* -------------------------------------------------------------------------- */
/* Service */
@ -55,8 +55,15 @@ export class IamService {
}
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, {
email: data.email
email,
});
}
}

View file

@ -61,6 +61,10 @@ export class UsersService {
return this.usersRepository.findOneByUsername(username);
}
async findOneByEmail(email: string) {
return this.usersRepository.findOneByEmail(email);
}
async findOneById(id: string) {
return this.usersRepository.findOneById(id);
}

View file

@ -13,6 +13,14 @@ export class WishlistsService {
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) {
return this.createEmpty(userId, null);
}

View file

@ -4,7 +4,7 @@ export async function parseApiResponse<T>(response: ClientResponse<T>) {
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
return response.ok
? { 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) {

View file

@ -28,7 +28,7 @@ export function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>
return z.preprocess(
(value) =>
typeof value === 'string'
? parseInt(value, 10)
? Number.parseInt(value, 10)
: typeof value === 'number'
? value
: undefined,

View file

@ -20,18 +20,19 @@ export const load: PageServerLoad = async (event) => {
if (!authedUser) {
throw redirect(302, '/login', notSignedInMessage, event);
}
console.log('authedUser', authedUser);
// if (userNotAuthenticated(user, session)) {
// redirect(302, '/login', notSignedInMessage, event);
// }
//
// const dbUser = await db.query.usersTable.findFirst({
// where: eq(usersTable.id, user!.id!),
// });
const profileForm = await superValidate(zod(profileSchema), {
defaults: {
firstName: authedUser?.first_name ?? '',
lastName: authedUser?.last_name ?? '',
firstName: authedUser?.firstName ?? '',
lastName: authedUser?.lastName ?? '',
username: authedUser?.username ?? '',
},
});
@ -72,8 +73,11 @@ export const actions: Actions = {
const form = await superValidate(event, zod(updateProfileDto));
const { error } = await locals.api.user.$post({ json: form.data }).then(locals.parseApiResponse);
if (error) return setError(form, 'username', error);
const { error } = await locals.api.me.update.profile.$put({ json: form.data }).then(locals.parwseApiResponse);
console.log('data from profile update', error);
if (error) {
return setError(form, 'username', error);
}
if (!form.valid) {
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');
return message(form, { type: 'success', message: 'Profile updated successfully!' });
},

View file

@ -4,23 +4,33 @@ 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 '../../../../../db';
import { db } from '$lib/server/api/infrastructure/database';
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';
export async function load(event) {
const { params, locals } = event;
const { user, session } = locals;
const { id } = params;
if (userNotAuthenticated(user, session)) {
redirect(302, '/login', notSignedInMessage, event);
const { cuid } = params;
const authedUser = await locals.getAuthedUser();
if (!authedUser) {
throw redirect(302, '/login', notSignedInMessage, event);
}
try {
const wishlist = await db.query.wishlists.findMany({
where: and(eq(wishlists.user_id, user!.id!), eq(wishlists.cuid, id)),
});
const { data, errors } = await locals.api.wishlists[':cuid'].$get({
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) {
redirect(302, '/404');

View file

@ -1,3 +1,4 @@
import { fail } from '@sveltejs/kit';
import type { MetaTagsProps } from 'svelte-meta-tags';
import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types';
@ -43,37 +44,24 @@ export const load: PageServerLoad = async (event) => {
});
if (authedUser) {
const dbUser = await db.query.usersTable.findFirst({
where: eq(usersTable.id, authedUser!.id!),
});
const { data: wishlistsData, error: wishlistsError } = await locals.api.wishlists.$get().then(locals.parseApiResponse);
const { data: collectionsData, error: collectionsError } = await locals.api.collections.$get().then(locals.parseApiResponse);
console.log('Sending back user details');
const userWishlists = await db.query.wishlists.findMany({
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!),
});
if (wishlistsError || collectionsError) {
return fail(500, 'Failed to fetch wishlists or collections');
}
console.log('Wishlists', userWishlists);
console.log('Collections', userCollection);
console.log('Wishlists', wishlistsData.wishlists);
console.log('Collections', collectionsData.collections);
return {
metaTagsChild: metaTags,
user: {
firstName: dbUser?.first_name,
lastName: dbUser?.last_name,
username: dbUser?.username,
firstName: authedUser?.firstName,
lastName: authedUser?.lastName,
username: authedUser?.username,
},
wishlists: userWishlists,
collections: userCollection,
wishlists: wishlistsData.wishlists,
collections: collectionsData.collections,
};
}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { page } from "$app/stores";
import { Button } from "$lib/components/ui/button";
import Logo from "$lib/components/logo.svelte";
import { page } from '$app/stores';
import { Button } from '$lib/components/ui/button';
import Logo from '$lib/components/logo.svelte';
import Transition from '$lib/components/transition.svelte';
let { data, children } = $props();
@ -15,21 +15,11 @@
Bored Game
</a>
<div class="auth-buttons">
{#if $page.url.pathname !== "/login"}
<Button
href="/login"
variant="ghost"
>
Login
</Button>
{#if $page.url.pathname !== '/login'}
<Button href="/login" variant="ghost">Login</Button>
{/if}
{#if $page.url.pathname !== "/sign-up"}
<Button
href="/sign-up"
variant="ghost"
>
Sign up
</Button>
{#if $page.url.pathname !== '/sign-up'}
<Button href="/sign-up" variant="ghost">Sign up</Button>
{/if}
</div>
<div class="auth-marketing">
@ -42,7 +32,8 @@
<div class="quote-wrapper">
<blockquote class="quote">
<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>
<footer>Bradley</footer>
</blockquote>
@ -63,7 +54,7 @@
min-height: 100vh;
@media (width >= 768px) {
display: grid
display: grid;
}
@media (width >= 1024px) {
padding-left: 0;
@ -135,7 +126,7 @@
font-size: 1.125rem;
line-height: 1.75rem;
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-duration: 300ms;
top: 1rem;
@ -152,7 +143,6 @@
@media (width <= 768px) {
position: absolute;
}
@media (width > 768px) {
@ -165,4 +155,4 @@
--fg: white;
}
}
</style>
</style>

View file

@ -1,23 +1,23 @@
import { fail, type Actions } from '@sveltejs/kit';
import { eq, or } from 'drizzle-orm';
import { Argon2id } from 'oslo/password';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { db } from '../../../lib/server/api/infrastructure/database/index';
import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia';
import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables';
import type { PageServerLoad } from './$types';
import {signinUsernameDto} from "$lib/dtos/signin-username.dto";
import { fail, type Actions } from '@sveltejs/kit'
import { eq, or } from 'drizzle-orm'
import { Argon2id } from 'oslo/password'
import { zod } from 'sveltekit-superforms/adapters'
import { setError, superValidate } from 'sveltekit-superforms/server'
import { redirect } from 'sveltekit-flash-message/server'
import { db } from '../../../lib/server/api/infrastructure/database/index'
import { lucia } from '../../../lib/server/api/infrastructure/auth/lucia'
import { credentialsTable, usersTable } from '../../../lib/server/api/infrastructure/database/tables'
import type { PageServerLoad } from './$types'
import { signinUsernameDto } from '$lib/dtos/signin-username.dto'
export const load: PageServerLoad = async (event) => {
const { locals } = event;
const { locals } = event
const authedUser = await locals.getAuthedUser();
const authedUser = await locals.getAuthedUser()
if (authedUser) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
const message = { type: 'success', message: 'You are already signed in' } as const
throw redirect('/', message, event)
}
// if (userFullyAuthenticated(user, session)) {
@ -31,34 +31,34 @@ export const load: PageServerLoad = async (event) => {
// ...sessionCookie.attributes,
// });
// }
const form = await superValidate(event, zod(signinUsernameDto));
const form = await superValidate(event, zod(signinUsernameDto))
return {
form,
};
};
}
}
export const actions: Actions = {
default: async (event) => {
const { locals } = event;
const { locals } = event
const authedUser = await locals.getAuthedUser();
const authedUser = await locals.getAuthedUser()
if (authedUser) {
const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event);
const message = { type: 'success', message: 'You are already signed in' } as const
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);
if (error) return setError(form, 'username', error);
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse)
if (error) return setError(form, 'username', error)
if (!form.valid) {
form.data.password = '';
form.data.password = ''
return fail(400, {
form,
});
})
}
// let session;
@ -75,59 +75,59 @@ export const actions: Actions = {
// let twoFactorDetails;
//
try {
// const password = form.data.password;
// console.log('user', JSON.stringify(user, null, 2));
//
// if (!user?.hashed_password) {
// console.log('invalid username/password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// const validPassword = await new Argon2id().verify(user.hashed_password, password);
// if (!validPassword) {
// console.log('invalid password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// console.log('ip', locals.ip);
// console.log('country', locals.country);
//
// twoFactorDetails = await db.query.twoFactor.findFirst({
// where: eq(twoFactor.userId, user?.id),
// });
//
// if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
// await db.update(twoFactor).set({
// initiatedTime: new Date(),
// });
//
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled:
// twoFactorDetails?.enabled &&
// twoFactorDetails?.secret !== null &&
// twoFactorDetails?.secret !== '',
// isTwoFactorAuthenticated: false,
// });
// } else {
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled: false,
// isTwoFactorAuthenticated: false,
// });
// }
// console.log('logging in session', session);
// sessionCookie = lucia.createSessionCookie(session.id);
// console.log('logging in session cookie', sessionCookie);
// const password = form.data.password;
// console.log('user', JSON.stringify(user, null, 2));
//
// if (!user?.hashed_password) {
// console.log('invalid username/password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// const validPassword = await new Argon2id().verify(user.hashed_password, password);
// if (!validPassword) {
// console.log('invalid password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// console.log('ip', locals.ip);
// console.log('country', locals.country);
//
// twoFactorDetails = await db.query.twoFactor.findFirst({
// where: eq(twoFactor.userId, user?.id),
// });
//
// if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
// await db.update(twoFactor).set({
// initiatedTime: new Date(),
// });
//
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled:
// twoFactorDetails?.enabled &&
// twoFactorDetails?.secret !== null &&
// twoFactorDetails?.secret !== '',
// isTwoFactorAuthenticated: false,
// });
// } else {
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled: false,
// isTwoFactorAuthenticated: false,
// });
// }
// console.log('logging in session', session);
// sessionCookie = lucia.createSessionCookie(session.id);
// console.log('logging in session cookie', sessionCookie);
} catch (e) {
// TODO: need to return error message to the client
console.error(e);
form.data.password = '';
return setError(form, '', 'Your username or password is incorrect.');
console.error(e)
form.data.password = ''
return setError(form, '', 'Your username or password is incorrect.')
}
// console.log('setting session cookie', sessionCookie);
@ -136,8 +136,8 @@ export const actions: Actions = {
// ...sessionCookie.attributes,
// });
form.data.username = '';
form.data.password = '';
form.data.username = ''
form.data.password = ''
// if (
// twoFactorDetails?.enabled &&
@ -152,4 +152,4 @@ export const actions: Actions = {
// redirect(302, '/', message, event);
// }
},
};
}

View file

@ -87,25 +87,4 @@
{/snippet}
<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>