mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Comment out collection until I can fix it. Rewrite wishlist, create users with the default role, add seed roles, update the user with the default role if they don't have it. And other things.
This commit is contained in:
parent
734cee7a29
commit
48c16d7892
28 changed files with 978 additions and 612 deletions
38
package.json
38
package.json
|
|
@ -21,36 +21,36 @@
|
|||
"seed": "ts-node --esm prisma/seed.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@playwright/test": "^1.35.1",
|
||||
"@rgossiaux/svelte-headlessui": "1.0.2",
|
||||
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
||||
"@sveltejs/adapter-auto": "^1.0.3",
|
||||
"@sveltejs/adapter-vercel": "^1.0.6",
|
||||
"@sveltejs/kit": "^1.19.0",
|
||||
"@sveltejs/kit": "^1.20.2",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/node": "^18.16.16",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@types/node": "^18.16.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||
"@typescript-eslint/parser": "^5.59.11",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-svelte": "^2.29.0",
|
||||
"eslint-plugin-svelte": "^2.30.0",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"postcss-preset-env": "^8.4.2",
|
||||
"postcss-preset-env": "^8.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.0",
|
||||
"prisma": "^4.14.1",
|
||||
"sass": "^1.62.1",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"prisma": "^4.15.0",
|
||||
"sass": "^1.63.4",
|
||||
"svelte": "^3.59.1",
|
||||
"svelte-check": "^2.10.3",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"sveltekit-superforms": "^0.8.7",
|
||||
"svelte-preprocess": "^5.0.4",
|
||||
"sveltekit-superforms": "^1.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.5.2",
|
||||
"tslib": "^2.5.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.3.9",
|
||||
"vitest": "^0.25.3",
|
||||
|
|
@ -64,21 +64,21 @@
|
|||
"dependencies": {
|
||||
"@axiomhq/axiom-node": "^0.12.0",
|
||||
"@fontsource/fira-mono": "^4.5.10",
|
||||
"@iconify-icons/line-md": "^1.2.22",
|
||||
"@iconify-icons/line-md": "^1.2.23",
|
||||
"@iconify-icons/mdi": "^1.2.46",
|
||||
"@leveluptuts/svelte-side-menu": "^1.0.5",
|
||||
"@leveluptuts/svelte-toy": "^2.0.3",
|
||||
"@lucia-auth/adapter-mysql": "^1.1.1",
|
||||
"@lucia-auth/adapter-prisma": "^2.0.0",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@prisma/client": "4.14.1",
|
||||
"@prisma/client": "4.15.0",
|
||||
"@types/feather-icons": "^4.29.1",
|
||||
"cookie": "^0.5.0",
|
||||
"feather-icons": "^4.29.0",
|
||||
"iconify-icon": "^1.0.7",
|
||||
"loader": "^2.1.1",
|
||||
"lucia-auth": "^1.7.0",
|
||||
"open-props": "^1.5.8",
|
||||
"lucia-auth": "^1.8.0",
|
||||
"open-props": "^1.5.9",
|
||||
"svelte-lazy": "^1.2.1",
|
||||
"svelte-lazy-loader": "^1.0.0",
|
||||
"svelte-legos": "^0.2.1",
|
||||
|
|
|
|||
848
pnpm-lock.yaml
848
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,7 @@ const config = {
|
|||
plugins: [
|
||||
atImport(),
|
||||
postcssPresetEnv({
|
||||
stage: 3,
|
||||
stage: 2,
|
||||
features: {
|
||||
'nesting-rules': true,
|
||||
'custom-media-queries': true,
|
||||
|
|
|
|||
|
|
@ -11,20 +11,39 @@ datasource db {
|
|||
relationMode = "prisma"
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
userRoles UserRole[]
|
||||
}
|
||||
|
||||
model UserRole {
|
||||
id String @id @default(cuid())
|
||||
user AuthUser @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
roleId String
|
||||
|
||||
@@unique([userId, roleId])
|
||||
@@index([userId])
|
||||
@@index([roleId])
|
||||
}
|
||||
|
||||
model AuthUser {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
email String? @unique
|
||||
email String? @unique
|
||||
firstName String?
|
||||
lastName String?
|
||||
role Role @default(USER)
|
||||
roles UserRole[]
|
||||
verified Boolean @default(false)
|
||||
receiveEmail Boolean @default(false)
|
||||
token String? @unique
|
||||
collection Collection?
|
||||
wishlist Wishlist[]
|
||||
createdAt DateTime @default(now()) @db.Timestamp(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamp(6)
|
||||
theme String @default("system")
|
||||
createdAt DateTime @default(now()) @db.Timestamp(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamp(6)
|
||||
auth_session AuthSession[]
|
||||
auth_key AuthKey[]
|
||||
|
||||
|
|
@ -54,11 +73,6 @@ model AuthKey {
|
|||
@@map("auth_key")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
USER
|
||||
ADMIN
|
||||
}
|
||||
|
||||
model Collection {
|
||||
id String @id @default(cuid())
|
||||
user_id String @unique
|
||||
|
|
|
|||
|
|
@ -1,31 +1,40 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import userData from '../src/lib/data.json' assert { type: 'json' };
|
||||
// import userData from '../src/lib/data.json' assert { type: 'json' };
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log(`Start seeding ...`);
|
||||
|
||||
for (const p of userData) {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
firstName: p.user.firstName,
|
||||
lastName: p.user.lastName,
|
||||
email: p.user.email,
|
||||
username: p.user.username
|
||||
}
|
||||
const existingRoles = await prisma.role.findMany();
|
||||
if (existingRoles.length === 0) {
|
||||
await prisma.role.createMany({
|
||||
data: [{ name: 'admin' }, { name: 'user' }]
|
||||
});
|
||||
console.log(`Created user with id: ${user.id}`);
|
||||
console.log('Roles created.');
|
||||
} else {
|
||||
console.log('Roles already exist. No action taken.');
|
||||
}
|
||||
|
||||
// for (const p of userData) {
|
||||
// const user = await prisma.user.create({
|
||||
// data: {
|
||||
// firstName: p.user.firstName,
|
||||
// lastName: p.user.lastName,
|
||||
// email: p.user.email,
|
||||
// username: p.user.username
|
||||
// }
|
||||
// });
|
||||
// console.log(`Created user with id: ${user.id}`);
|
||||
// }
|
||||
console.log(`Seeding finished.`);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
|
|
|||
1
src/app.d.ts
vendored
1
src/app.d.ts
vendored
|
|
@ -9,6 +9,7 @@ declare global {
|
|||
}
|
||||
interface Locals {
|
||||
auth: import('lucia-auth').AuthRequest;
|
||||
prisma: PrismaClient;
|
||||
user: Lucia.UserAttributes;
|
||||
startTimer: number;
|
||||
error: string;
|
||||
|
|
|
|||
32
src/db/roles.ts
Normal file
32
src/db/roles.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import prisma from '$lib/prisma';
|
||||
|
||||
export async function add_user_to_role(userId: string, roleName: string) {
|
||||
// Find the role by its name
|
||||
const role = await prisma.role.findUnique({
|
||||
where: {
|
||||
name: roleName
|
||||
}
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
throw new Error(`Role with name ${roleName} not found`);
|
||||
}
|
||||
|
||||
// Create a UserRole entry linking the user and the role
|
||||
const userRole = await prisma.userRole.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId
|
||||
}
|
||||
},
|
||||
role: {
|
||||
connect: {
|
||||
id: role.id
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return userRole;
|
||||
}
|
||||
56
src/db/users.ts
Normal file
56
src/db/users.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { auth } from '$lib/server/lucia';
|
||||
import prisma from '$lib/prisma';
|
||||
import type { AuthUser } from '@prisma/client';
|
||||
import { add_user_to_role } from './roles';
|
||||
|
||||
export function create_user(user: AuthUser) {
|
||||
return prisma.authUser.create({
|
||||
data: {
|
||||
username: user.username
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function find_or_create_user(user: AuthUser) {
|
||||
const existing_user = await prisma.authUser.findUnique({
|
||||
where: {
|
||||
username: user.username
|
||||
}
|
||||
});
|
||||
if (existing_user) {
|
||||
return existing_user;
|
||||
} else {
|
||||
const new_user = await create_user(user);
|
||||
add_user_to_role(new_user.id, 'user');
|
||||
return new_user;
|
||||
}
|
||||
}
|
||||
|
||||
export async function find_user_with_roles(user_id: string) {
|
||||
const user_with_roles = await prisma.authUser.findUnique({
|
||||
where: {
|
||||
id: user_id
|
||||
},
|
||||
include: {
|
||||
roles: {
|
||||
select: {
|
||||
role: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!user_with_roles) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const user = {
|
||||
...user_with_roles,
|
||||
roles: user_with_roles.roles.map((user_role) => user_role.role.name)
|
||||
};
|
||||
|
||||
return user;
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { redirect, type Handle } from '@sveltejs/kit';
|
||||
import type { HandleServerError } from '@sveltejs/kit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { redirect, type HandleServerError, type Handle } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
import { auth } from '$lib/server/lucia';
|
||||
import log from '$lib/server/log';
|
||||
import { dev } from '$app/environment';
|
||||
import prisma from '$lib/config/prisma';
|
||||
|
||||
export const handleError: HandleServerError = async ({ error, event }) => {
|
||||
const errorId = crypto.randomUUID();
|
||||
|
|
@ -24,24 +25,28 @@ export const handleError: HandleServerError = async ({ error, event }) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// export const prismaClient: Handle = async function ({ event, resolve }) {
|
||||
// event.locals.prisma = prisma;
|
||||
// const response = await resolve(event);
|
||||
// return response;
|
||||
// };
|
||||
|
||||
export const authentication: Handle = async function ({ event, resolve }) {
|
||||
const startTimer = Date.now();
|
||||
event.locals.startTimer = startTimer;
|
||||
|
||||
event.locals.auth = auth.handleRequest(event);
|
||||
console.log(JSON.stringify(event));
|
||||
if (event.locals?.auth) {
|
||||
const { user } = await event.locals.auth.validateUser();
|
||||
event.locals.user = user;
|
||||
if (event.route.id?.startsWith('/(protected)')) {
|
||||
if (!user) throw redirect(302, '/auth/sign-in');
|
||||
if (!user.verified) throw redirect(302, '/auth/verify/email');
|
||||
}
|
||||
// if (event.route.id?.startsWith('/(protected)')) {
|
||||
// if (!user) throw redirect(302, '/auth/sign-in');
|
||||
// if (!user.verified) throw redirect(302, '/auth/verify/email');
|
||||
// }
|
||||
}
|
||||
|
||||
const response = await resolve(event);
|
||||
if (!dev) {
|
||||
log(response.status, event);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
export const handle = sequence(authentication);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import Profile from '../preferences/profile.svelte';
|
||||
// import Profile from '../preferences/profile.svelte';
|
||||
import logo from './bored-game.png';
|
||||
|
||||
export let user: any;
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
</div>
|
||||
<!-- <TextSearch /> -->
|
||||
<nav>
|
||||
<a href="/collection" title="Go to your collection" data-sveltekit-preload-data>Collection</a>
|
||||
<a href="/wishlist" title="Go to your wishlist" data-sveltekit-preload-data>Wishlist</a>
|
||||
{#if user}
|
||||
<a href="/collection" title="Go to your collection" data-sveltekit-preload-data>Collection</a>
|
||||
<a href="/wishlist" title="Go to your wishlist" data-sveltekit-preload-data>Wishlist</a>
|
||||
<form
|
||||
use:enhance
|
||||
action="/auth/signout"
|
||||
|
|
@ -36,11 +36,11 @@
|
|||
<span class="flex-auto">Sign Up</span></a
|
||||
>
|
||||
{/if}
|
||||
<Profile />
|
||||
<!-- <Profile /> -->
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="postcss">
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -88,7 +88,6 @@
|
|||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
/* font-size: 0.8rem; */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
text-decoration: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Validation } from 'sveltekit-superforms/index';
|
||||
import type { SuperValidated } from 'sveltekit-superforms/index';
|
||||
import type { SearchSchema } from '$lib/zodValidation';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import { gameStore } from '$lib/stores/gameSearchStore';
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import { toast } from '../../toast/toast';
|
||||
|
||||
export let data: Validation<SearchSchema>;
|
||||
export let data: SuperValidated<SearchSchema>;
|
||||
const { enhance } = superForm(data, {
|
||||
onSubmit: () => {
|
||||
gameStore.removeAll();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { applyAction, type SubmitFunction } from '$app/forms';
|
||||
import { superForm } from 'sveltekit-superforms/client';
|
||||
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||
import type { Validation } from 'sveltekit-superforms/index';
|
||||
import type { SuperValidated } from 'sveltekit-superforms/index';
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@rgossiaux/svelte-headlessui';
|
||||
import { ChevronRightIcon } from '@rgossiaux/svelte-heroicons/solid';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import Game from '$lib/components/game/index.svelte';
|
||||
import { ToastType, type GameType, type SavedGameType } from '$lib/types';
|
||||
import SkeletonPlaceholder from '../../SkeletonPlaceholder.svelte';
|
||||
// import SkeletonPlaceholder from '../../SkeletonPlaceholder.svelte';
|
||||
import RemoveCollectionDialog from '../../dialog/RemoveCollectionDialog.svelte';
|
||||
import RemoveWishlistDialog from '../../dialog/RemoveWishlistDialog.svelte';
|
||||
import type { SearchSchema } from '$lib/zodValidation';
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
detail: GameType | SavedGameType;
|
||||
}
|
||||
|
||||
export let data: Validation<SearchSchema>;
|
||||
export let data: SuperValidated<SearchSchema>;
|
||||
const { form, constraints, errors } = superForm(data, {
|
||||
onSubmit: () => {
|
||||
boredState.update((n) => ({ ...n, loading: true }));
|
||||
|
|
@ -213,11 +213,11 @@
|
|||
<div class="games">
|
||||
<h1>Games Found:</h1>
|
||||
<div class="games-list">
|
||||
{#each placeholderList as game, i}
|
||||
<!-- {#each placeholderList as game, i}
|
||||
<SkeletonPlaceholder
|
||||
style="width: 100%; height: 500px; border-radius: var(--borderRadius);"
|
||||
/>
|
||||
{/each}
|
||||
{/each} -->
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import prisma from '@lucia-auth/adapter-prisma';
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
const client = new PrismaClient();
|
||||
|
||||
export const auth = lucia({
|
||||
adapter: prisma(client),
|
||||
adapter: prisma(new PrismaClient()),
|
||||
env: dev ? 'DEV' : 'PROD',
|
||||
middleware: sveltekit(),
|
||||
transformDatabaseUser: (userData) => {
|
||||
|
|
@ -23,6 +21,9 @@ export const auth = lucia({
|
|||
receiveEmail: userData.receiveEmail,
|
||||
token: userData.token
|
||||
};
|
||||
},
|
||||
experimental: {
|
||||
debugMode: false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ export const saved_game_schema = z.object({
|
|||
playtime: IntegerString(z.number())
|
||||
});
|
||||
|
||||
export const list_game_request_schema = z.object({
|
||||
id: z.string(),
|
||||
externalId: z.string()
|
||||
});
|
||||
|
||||
export type ListGameSchema = typeof list_game_request_schema;
|
||||
|
||||
// https://github.com/colinhacks/zod/discussions/330
|
||||
function IntegerString<schema extends ZodNumber | ZodOptional<ZodNumber>>(schema: schema) {
|
||||
return z.preprocess(
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
<Analytics />
|
||||
{/if}
|
||||
|
||||
{#if dev}
|
||||
<!-- {#if dev}
|
||||
<Toy
|
||||
register={{
|
||||
boredState,
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
toast
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if} -->
|
||||
|
||||
<div class="wrapper">
|
||||
<Header user={data.user} />
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
import Random from '$lib/components/random/index.svelte';
|
||||
|
||||
export let data;
|
||||
export let formData;
|
||||
console.log('formData', formData);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
|
|||
5
src/routes/admin/+layout.server.ts
Normal file
5
src/routes/admin/+layout.server.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load = async function ({ locals }) {
|
||||
if (!locals?.user?.role?.includes('admin')) throw redirect(302, '/');
|
||||
};
|
||||
1
src/routes/admin/+layout.svelte
Normal file
1
src/routes/admin/+layout.svelte
Normal file
|
|
@ -0,0 +1 @@
|
|||
<slot />
|
||||
0
src/routes/admin/+page.svelte
Normal file
0
src/routes/admin/+page.svelte
Normal file
|
|
@ -1,7 +1,9 @@
|
|||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { auth } from '$lib/server/lucia';
|
||||
import prisma from '$lib/prisma.js';
|
||||
import { userSchema } from '$lib/config/zod-schemas';
|
||||
import { add_user_to_role } from '$db/roles';
|
||||
|
||||
const signInSchema = userSchema.pick({
|
||||
username: true,
|
||||
|
|
@ -13,10 +15,10 @@ export const load = async (event) => {
|
|||
if (session) {
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
// const form = await superValidate(event, signInSchema);
|
||||
// return {
|
||||
// form
|
||||
// };
|
||||
const form = await superValidate(event, signInSchema);
|
||||
return {
|
||||
form
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
|
|
@ -30,16 +32,32 @@ export const actions = {
|
|||
});
|
||||
}
|
||||
|
||||
// Adding user to the db
|
||||
try {
|
||||
const key = await auth.useKey('username', form.data.username, form.data.password);
|
||||
const session = await auth.createSession(key.userId);
|
||||
event.locals.auth.setSession(session);
|
||||
|
||||
const user = await prisma.authUser.findUnique({
|
||||
where: {
|
||||
id: session.userId
|
||||
},
|
||||
include: {
|
||||
roles: {
|
||||
select: {
|
||||
role: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (user && user.roles.length === 0) {
|
||||
add_user_to_role(user.id, 'user');
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: need to return error message to the client
|
||||
console.error(e);
|
||||
form.data.password = '';
|
||||
return setError(form, null, 'The username or password is incorrect.');
|
||||
return setError(form, '', 'The username or password is incorrect.');
|
||||
}
|
||||
form.data.username = '';
|
||||
form.data.password = '';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { userSchema } from '$lib/config/zod-schemas.js';
|
||||
|
||||
export let data;
|
||||
|
||||
const signInSchema = userSchema.pick({ username: true, password: true });
|
||||
const { form, errors, enhance, delayed } = superForm(data.form, {
|
||||
taintedMessage: null,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { fail, redirect } from '@sveltejs/kit';
|
|||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { auth } from '$lib/server/lucia';
|
||||
import { userSchema } from '$lib/config/zod-schemas';
|
||||
import { add_user_to_role } from '$db/roles';
|
||||
|
||||
const signUpSchema = userSchema.pick({
|
||||
firstName: true,
|
||||
|
|
@ -55,6 +56,7 @@ export const actions = {
|
|||
token
|
||||
}
|
||||
});
|
||||
add_user_to_role(user.id, 'user');
|
||||
|
||||
console.log('User', user);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,65 @@
|
|||
import type { PageServerLoad } from "../$types";
|
||||
// import { redirect } from '@sveltejs/kit';
|
||||
// import { superValidate } from 'sveltekit-superforms/server';
|
||||
// import { search_schema } from '$lib/zodValidation';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||
const searchParams = Object.fromEntries(url?.searchParams);
|
||||
const q = searchParams?.q;
|
||||
const limit = parseInt(searchParams?.limit) || 10;
|
||||
const skip = parseInt(searchParams?.skip) || 0;
|
||||
export const load = async ({ fetch, url, locals }) => {
|
||||
// const session = await locals.auth.validate();
|
||||
// if (!session) {
|
||||
// throw redirect(302, '/auth/signin');
|
||||
// }
|
||||
|
||||
console.log('locals load', locals);
|
||||
// const searchParams = Object.fromEntries(url?.searchParams);
|
||||
// const q = searchParams?.q;
|
||||
// const limit = parseInt(searchParams?.limit) || 10;
|
||||
// const skip = parseInt(searchParams?.skip) || 0;
|
||||
|
||||
// const searchData = {
|
||||
// q,
|
||||
// limit,
|
||||
// skip
|
||||
// };
|
||||
|
||||
// const form = await superValidate(searchData, search_schema);
|
||||
|
||||
try {
|
||||
// let collection = await locals.prisma.collection.findUnique({
|
||||
// where: {
|
||||
// user_id: session.userId
|
||||
// }
|
||||
// include: {
|
||||
// collectionItems: {
|
||||
// where: {
|
||||
// title: {
|
||||
// contains: q,
|
||||
// mode: 'insensitive'
|
||||
// }
|
||||
// },
|
||||
// take: limit,
|
||||
// skip
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// console.log('collection', collection);
|
||||
// if (!collection) {
|
||||
// collection = await locals.prisma.collection.create({
|
||||
// data: {
|
||||
// user_id: session.userId
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
// form,
|
||||
// collection
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return {
|
||||
q,
|
||||
limit,
|
||||
skip
|
||||
// form,
|
||||
// collection: []
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,78 +1,78 @@
|
|||
<script lang="ts">
|
||||
import { tick, onDestroy } from 'svelte';
|
||||
import Game from '$lib/components/game/index.svelte';
|
||||
import { collectionStore } from '$lib/stores/collectionStore';
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
|
||||
import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
import { createSearchStore, searchHandler } from '$lib/stores/search';
|
||||
import type { PageData } from './$types';
|
||||
// import { tick, onDestroy } from 'svelte';
|
||||
// import Game from '$lib/components/game/index.svelte';
|
||||
// import { collectionStore } from '$lib/stores/collectionStore';
|
||||
// import type { GameType, SavedGameType } from '$lib/types';
|
||||
// import { boredState } from '$lib/stores/boredState';
|
||||
// import Pagination from '$lib/components/pagination/index.svelte';
|
||||
// import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
|
||||
// import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
// import { createSearchStore, searchHandler } from '$lib/stores/search';
|
||||
|
||||
export let data: PageData;
|
||||
console.log(`Page data: ${JSON.stringify(data)}`)
|
||||
export let data;
|
||||
console.log(`Page data: ${JSON.stringify(data)}`);
|
||||
// let collectionItems = data?.collection?.collectionItems;
|
||||
|
||||
let gameToRemove: GameType | SavedGameType;
|
||||
let pageSize = 10;
|
||||
let page = 1;
|
||||
// let gameToRemove: GameType | SavedGameType;
|
||||
// let pageSize = 10;
|
||||
// let page = 1;
|
||||
|
||||
const searchStore = createSearchStore($collectionStore);
|
||||
console.log('searchStore', $searchStore);
|
||||
// const searchStore = createSearchStore($collectionStore);
|
||||
// console.log('searchStore', $searchStore);
|
||||
|
||||
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
|
||||
// const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe();
|
||||
});
|
||||
// onDestroy(() => {
|
||||
// unsubscribe();
|
||||
// });
|
||||
|
||||
$: skip = (page - 1) * pageSize;
|
||||
$: gamesShown = $searchStore.data.slice(skip, skip + pageSize);
|
||||
$: totalItems = $searchStore.search === '' ? $collectionStore.length : $searchStore.filtered.length;
|
||||
// $: skip = (page - 1) * pageSize;
|
||||
// $: gamesShown = $searchStore.data.slice(skip, skip + pageSize);
|
||||
// $: totalItems = $searchStore.search === '' ? $collectionStore.length : $searchStore.filtered.length;
|
||||
|
||||
interface RemoveGameEvent extends Event {
|
||||
detail: GameType | SavedGameType;
|
||||
}
|
||||
// interface RemoveGameEvent extends Event {
|
||||
// detail: GameType | SavedGameType;
|
||||
// }
|
||||
|
||||
function handleRemoveCollection(event: RemoveGameEvent) {
|
||||
console.log('Remove collection event handler');
|
||||
console.log('event', event);
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
// function handleRemoveCollection(event: RemoveGameEvent) {
|
||||
// console.log('Remove collection event handler');
|
||||
// console.log('event', event);
|
||||
// gameToRemove = event?.detail;
|
||||
// boredState.update((n) => ({
|
||||
// ...n,
|
||||
// dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
|
||||
// }));
|
||||
// }
|
||||
|
||||
function handleRemoveWishlist(event: RemoveGameEvent) {
|
||||
console.log('Remove wishlist event handler');
|
||||
console.log('event', event);
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
// function handleRemoveWishlist(event: RemoveGameEvent) {
|
||||
// console.log('Remove wishlist event handler');
|
||||
// console.log('event', event);
|
||||
// gameToRemove = event?.detail;
|
||||
// boredState.update((n) => ({
|
||||
// ...n,
|
||||
// dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
|
||||
// }));
|
||||
// }
|
||||
|
||||
async function handleNextPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page + 1) {
|
||||
page += 1;
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
// async function handleNextPageEvent(event: CustomEvent) {
|
||||
// if (+event?.detail?.page === page + 1) {
|
||||
// page += 1;
|
||||
// }
|
||||
// await tick();
|
||||
// }
|
||||
|
||||
async function handlePreviousPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page - 1) {
|
||||
page -= 1;
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
// async function handlePreviousPageEvent(event: CustomEvent) {
|
||||
// if (+event?.detail?.page === page - 1) {
|
||||
// page -= 1;
|
||||
// }
|
||||
// await tick();
|
||||
// }
|
||||
|
||||
async function handlePerPageEvent(event: CustomEvent) {
|
||||
page = 1;
|
||||
pageSize = event.detail.pageSize;
|
||||
await tick();
|
||||
}
|
||||
// async function handlePerPageEvent(event: CustomEvent) {
|
||||
// page = 1;
|
||||
// pageSize = event.detail.pageSize;
|
||||
// await tick();
|
||||
// }
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -80,9 +80,9 @@
|
|||
</svelte:head>
|
||||
|
||||
<h1>Your Collection</h1>
|
||||
<input type="text" id="search" name="search" placeholder="Search Your Collection" bind:value={$searchStore.search} />
|
||||
<!-- <input type="text" id="search" name="search" placeholder="Search Your Collection" bind:value={$searchStore.search} /> -->
|
||||
|
||||
<div class="games">
|
||||
<!-- <div class="games">
|
||||
<div class="games-list">
|
||||
{#if $collectionStore.length === 0}
|
||||
<h2>No games in your collection</h2>
|
||||
|
|
@ -109,9 +109,9 @@
|
|||
on:perPageEvent={handlePerPageEvent}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="postcss">
|
||||
h1 {
|
||||
margin: 1.5rem 0rem;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@
|
|||
margin: 1rem;
|
||||
place-items: center;
|
||||
|
||||
@media (max-width: env(--medium-viewport)) {
|
||||
@media (max-width: 700px) {
|
||||
grid-template-columns: 1fr;
|
||||
place-items: center;
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@
|
|||
margin: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: env(--xsmall-viewport)) {
|
||||
@media (max-width: 500px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
95
src/routes/wishlist/+page.server.ts
Normal file
95
src/routes/wishlist/+page.server.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import prisma from '$lib/prisma.js';
|
||||
import { list_game_request_schema } from '$lib/zodValidation';
|
||||
|
||||
export async function load({ params, locals }) {
|
||||
const session = await locals.auth.validate();
|
||||
if (!session) {
|
||||
throw redirect(302, '/auth/signin');
|
||||
}
|
||||
|
||||
try {
|
||||
let wishlists = await prisma.wishlist.findMany({
|
||||
where: {
|
||||
user_id: session.userId
|
||||
},
|
||||
include: {
|
||||
items: true
|
||||
}
|
||||
});
|
||||
|
||||
if (wishlists.length === 0) {
|
||||
const wishlist = await prisma.wishlist.create({
|
||||
data: {
|
||||
user_id: session.userId,
|
||||
name: 'My Wishlist'
|
||||
}
|
||||
});
|
||||
wishlists.push(wishlist);
|
||||
}
|
||||
|
||||
return {
|
||||
wishlists
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
// Add game to a wishlist
|
||||
add: async (event) => {
|
||||
const { params, locals, request } = event;
|
||||
const form = await superValidate(event, list_game_request_schema);
|
||||
|
||||
const session = await locals.auth.validate();
|
||||
if (!session) {
|
||||
throw redirect(302, '/auth/signin');
|
||||
}
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
where: {
|
||||
id: form.id
|
||||
}
|
||||
});
|
||||
|
||||
// if (!game) {
|
||||
// throw redirect(302, '/404');
|
||||
// }
|
||||
|
||||
if (game) {
|
||||
const wishlist = await prisma.wishlist.create({
|
||||
data: {
|
||||
user_id: session.userId,
|
||||
name: form.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
form
|
||||
};
|
||||
},
|
||||
// Create new wishlist
|
||||
create: async ({ params, locals, request }) => {
|
||||
const session = await locals.auth.validate();
|
||||
if (!session) {
|
||||
throw redirect(302, '/auth/signin');
|
||||
}
|
||||
},
|
||||
// Delete a wishlist
|
||||
delete: async ({ params, locals, request }) => {
|
||||
const session = await locals.auth.validate();
|
||||
if (!session) {
|
||||
throw redirect(302, '/auth/signin');
|
||||
}
|
||||
},
|
||||
// Remove game from a wishlist
|
||||
remove: async ({ params, locals, request }) => {
|
||||
const session = await locals.auth.validate();
|
||||
if (!session) {
|
||||
throw redirect(302, '/auth/signin');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,70 +1,72 @@
|
|||
<script lang="ts">
|
||||
import { tick, onDestroy } from 'svelte';
|
||||
import Game from '$lib/components/game/index.svelte';
|
||||
import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
import type { GameType, SavedGameType } from '$lib/types';
|
||||
import { boredState } from '$lib/stores/boredState';
|
||||
import Pagination from '$lib/components/pagination/index.svelte';
|
||||
import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
|
||||
import { createSearchStore, searchHandler } from '$lib/stores/search';
|
||||
// import { tick, onDestroy } from 'svelte';
|
||||
// import Game from '$lib/components/game/index.svelte';
|
||||
// import { wishlistStore } from '$lib/stores/wishlistStore';
|
||||
// import type { GameType, SavedGameType } from '$lib/types';
|
||||
// import { boredState } from '$lib/stores/boredState';
|
||||
// import Pagination from '$lib/components/pagination/index.svelte';
|
||||
// import RemoveWishlistDialog from '$lib/components/dialog/RemoveWishlistDialog.svelte';
|
||||
// import RemoveCollectionDialog from '$lib/components/dialog/RemoveCollectionDialog.svelte';
|
||||
// import { createSearchStore, searchHandler } from '$lib/stores/search';
|
||||
|
||||
let gameToRemove: GameType | SavedGameType;
|
||||
let pageSize = 10;
|
||||
let page = 1;
|
||||
// let gameToRemove: GameType | SavedGameType;
|
||||
// let pageSize = 10;
|
||||
// let page = 1;
|
||||
|
||||
const searchStore = createSearchStore($wishlistStore);
|
||||
console.log('searchStore', $searchStore);
|
||||
// const searchStore = createSearchStore($wishlistStore);
|
||||
// console.log('searchStore', $searchStore);
|
||||
|
||||
const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
|
||||
// const unsubscribe = searchStore.subscribe((model) => searchHandler(model));
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe();
|
||||
});
|
||||
// onDestroy(() => {
|
||||
// unsubscribe();
|
||||
// });
|
||||
|
||||
$: skip = (page - 1) * pageSize;
|
||||
$: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
|
||||
$: totalItems = $searchStore.search === '' ? $wishlistStore.length : $searchStore.filtered.length;
|
||||
// $: skip = (page - 1) * pageSize;
|
||||
// $: gamesShown = $searchStore.filtered.slice(skip, skip + pageSize);
|
||||
// $: totalItems = $searchStore.search === '' ? $wishlistStore.length : $searchStore.filtered.length;
|
||||
|
||||
interface RemoveGameEvent extends Event {
|
||||
detail: GameType | SavedGameType;
|
||||
}
|
||||
// interface RemoveGameEvent extends Event {
|
||||
// detail: GameType | SavedGameType;
|
||||
// }
|
||||
|
||||
function handleRemoveCollection(event: RemoveGameEvent) {
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
// function handleRemoveCollection(event: RemoveGameEvent) {
|
||||
// gameToRemove = event?.detail;
|
||||
// boredState.update((n) => ({
|
||||
// ...n,
|
||||
// dialog: { isOpen: true, content: RemoveCollectionDialog, additionalData: gameToRemove }
|
||||
// }));
|
||||
// }
|
||||
|
||||
function handleRemoveWishlist(event: RemoveGameEvent) {
|
||||
gameToRemove = event?.detail;
|
||||
boredState.update((n) => ({
|
||||
...n,
|
||||
dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
|
||||
}));
|
||||
}
|
||||
// function handleRemoveWishlist(event: RemoveGameEvent) {
|
||||
// gameToRemove = event?.detail;
|
||||
// boredState.update((n) => ({
|
||||
// ...n,
|
||||
// dialog: { isOpen: true, content: RemoveWishlistDialog, additionalData: gameToRemove }
|
||||
// }));
|
||||
// }
|
||||
|
||||
async function handleNextPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page + 1) {
|
||||
page += 1;
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
// async function handleNextPageEvent(event: CustomEvent) {
|
||||
// if (+event?.detail?.page === page + 1) {
|
||||
// page += 1;
|
||||
// }
|
||||
// await tick();
|
||||
// }
|
||||
|
||||
async function handlePreviousPageEvent(event: CustomEvent) {
|
||||
if (+event?.detail?.page === page - 1) {
|
||||
page -= 1;
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
// async function handlePreviousPageEvent(event: CustomEvent) {
|
||||
// if (+event?.detail?.page === page - 1) {
|
||||
// page -= 1;
|
||||
// }
|
||||
// await tick();
|
||||
// }
|
||||
|
||||
async function handlePerPageEvent(event: CustomEvent) {
|
||||
page = 1;
|
||||
pageSize = event.detail.pageSize;
|
||||
await tick();
|
||||
}
|
||||
// async function handlePerPageEvent(event: CustomEvent) {
|
||||
// page = 1;
|
||||
// pageSize = event.detail.pageSize;
|
||||
// await tick();
|
||||
// }
|
||||
export let data;
|
||||
const wishlists = data.wishlists || [];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -72,7 +74,10 @@
|
|||
</svelte:head>
|
||||
|
||||
<h1>Your Wishlist</h1>
|
||||
<input type="text" id="search" name="search" placeholder="Search Your Wishlist" bind:value={$searchStore.search} />
|
||||
{#each wishlists as wishlist}
|
||||
<h2>{wishlist.name}</h2>
|
||||
{/each}
|
||||
<!-- <input type="text" id="search" name="search" placeholder="Search Your Wishlist" bind:value={$searchStore.search} />
|
||||
|
||||
<div class="games">
|
||||
<div class="games-list">
|
||||
|
|
@ -101,7 +106,7 @@
|
|||
on:perPageEvent={handlePerPageEvent}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<style lang="scss">
|
||||
h1 {
|
||||
|
|
|
|||
|
|
@ -6,22 +6,18 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
|
|||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: [
|
||||
vitePreprocess({
|
||||
postcss: true
|
||||
}),
|
||||
preprocess({
|
||||
postcss: true
|
||||
})
|
||||
],
|
||||
preprocess: [vitePreprocess()],
|
||||
vitePlugin: {
|
||||
inspector: true,
|
||||
},
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
$db: './src/db',
|
||||
$assets: './src/assets',
|
||||
$lib: './src/lib',
|
||||
$styles: './src/styles',
|
||||
$themes: './src/themes'
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue