Fixing get all wishlists, style wishlist cards, and fix TOTP.

This commit is contained in:
Bradley Shellnut 2024-10-01 17:09:56 -07:00
parent fc4396ccdd
commit 4032838f49
12 changed files with 68 additions and 68 deletions

View file

@ -80,7 +80,7 @@
"@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",
"@internationalized/date": "^3.5.5", "@internationalized/date": "^3.5.6",
"@lucia-auth/adapter-drizzle": "^1.1.0", "@lucia-auth/adapter-drizzle": "^1.1.0",
"@lukeed/uuid": "^2.0.1", "@lukeed/uuid": "^2.0.1",
"@neondatabase/serverless": "^0.9.5", "@neondatabase/serverless": "^0.9.5",
@ -97,7 +97,7 @@
"@types/feather-icons": "^4.29.4", "@types/feather-icons": "^4.29.4",
"bits-ui": "^0.21.13", "bits-ui": "^0.21.13",
"boardgamegeekclient": "^1.9.1", "boardgamegeekclient": "^1.9.1",
"bullmq": "^5.14.0", "bullmq": "^5.15.0",
"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",

View file

@ -27,8 +27,8 @@ importers:
specifier: ^1.2.48 specifier: ^1.2.48
version: 1.2.48 version: 1.2.48
'@internationalized/date': '@internationalized/date':
specifier: ^3.5.5 specifier: ^3.5.6
version: 3.5.5 version: 3.5.6
'@lucia-auth/adapter-drizzle': '@lucia-auth/adapter-drizzle':
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0(pg-native@3.2.0))(postgres@3.4.4))(lucia@3.2.0) version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0(pg-native@3.2.0))(postgres@3.4.4))(lucia@3.2.0)
@ -78,8 +78,8 @@ importers:
specifier: ^1.9.1 specifier: ^1.9.1
version: 1.9.1 version: 1.9.1
bullmq: bullmq:
specifier: ^5.14.0 specifier: ^5.15.0
version: 5.14.0 version: 5.15.0
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@ -1415,8 +1415,8 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@internationalized/date@3.5.5': '@internationalized/date@3.5.6':
resolution: {integrity: sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==} resolution: {integrity: sha512-jLxQjefH9VI5P9UQuqB6qNKnvFt1Ky1TPIzHGsIlCi7sZZoMR8SdYbBGRvM0y+Jtb+ez4ieBzmiAUcpmPYpyOw==}
'@ioredis/commands@1.2.0': '@ioredis/commands@1.2.0':
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
@ -2366,8 +2366,8 @@ packages:
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
bullmq@5.14.0: bullmq@5.15.0:
resolution: {integrity: sha512-qxZHtRuGEp0oHM1aNokuZ4gA0xr6vcZQPe1OLuQoDTuhaEXB4faxApUoo85v/PHnzrniAAqNT9kqD+UBbmECDQ==} resolution: {integrity: sha512-h53shVjx8s6wxYGtUfzAfENpSP7N5T0D4PMTvbZncozLjb8yUKhopfpa7PmcpQfq7SSO9dm/OZ9XQuGOCSGNug==}
bytes@3.1.2: bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
@ -5668,7 +5668,7 @@ snapshots:
'@img/sharp-win32-x64@0.33.5': '@img/sharp-win32-x64@0.33.5':
optional: true optional: true
'@internationalized/date@3.5.5': '@internationalized/date@3.5.6':
dependencies: dependencies:
'@swc/helpers': 0.5.13 '@swc/helpers': 0.5.13
@ -5746,7 +5746,7 @@ snapshots:
dependencies: dependencies:
'@floating-ui/core': 1.6.7 '@floating-ui/core': 1.6.7
'@floating-ui/dom': 1.6.10 '@floating-ui/dom': 1.6.10
'@internationalized/date': 3.5.5 '@internationalized/date': 3.5.6
dequal: 2.0.3 dequal: 2.0.3
focus-trap: 7.5.4 focus-trap: 7.5.4
nanoid: 5.0.7 nanoid: 5.0.7
@ -5756,7 +5756,7 @@ snapshots:
dependencies: dependencies:
'@floating-ui/core': 1.6.7 '@floating-ui/core': 1.6.7
'@floating-ui/dom': 1.6.10 '@floating-ui/dom': 1.6.10
'@internationalized/date': 3.5.5 '@internationalized/date': 3.5.6
dequal: 2.0.3 dequal: 2.0.3
focus-trap: 7.5.4 focus-trap: 7.5.4
nanoid: 5.0.7 nanoid: 5.0.7
@ -6577,7 +6577,7 @@ snapshots:
bits-ui@0.21.15(svelte@5.0.0-next.175): bits-ui@0.21.15(svelte@5.0.0-next.175):
dependencies: dependencies:
'@internationalized/date': 3.5.5 '@internationalized/date': 3.5.6
'@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175) '@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175)
nanoid: 5.0.7 nanoid: 5.0.7
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
@ -6631,7 +6631,7 @@ snapshots:
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
bullmq@5.14.0: bullmq@5.15.0:
dependencies: dependencies:
cron-parser: 4.9.0 cron-parser: 4.9.0
ioredis: 5.4.1 ioredis: 5.4.1

View file

@ -48,6 +48,11 @@ export class CollectionsRepository {
async findAllByUserId(userId: string, db = this.drizzle.db) { async findAllByUserId(userId: string, db = this.drizzle.db) {
return db.query.collections.findMany({ return db.query.collections.findMany({
where: eq(collections.user_id, userId), where: eq(collections.user_id, userId),
columns: {
cuid: true,
name: true,
createdAt: true,
},
}) })
} }

View file

@ -16,7 +16,7 @@ export class RecoveryCodesRepository {
} }
async findAllByUserId(userId: string, db = this.drizzle.db) { async findAllByUserId(userId: string, db = this.drizzle.db) {
return db.query.recoveryCodesTable.findFirst({ return db.query.recoveryCodesTable.findMany({
where: eq(recoveryCodesTable.userId, userId), where: eq(recoveryCodesTable.userId, userId),
}) })
} }

View file

@ -51,6 +51,7 @@ export class WishlistsRepository {
columns: { columns: {
cuid: true, cuid: true,
name: true, name: true,
createdAt: true,
}, },
}) })
} }

View file

@ -1,5 +1,4 @@
import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository'
import { HMAC } from 'oslo/crypto'
import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding' import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding'
import { verifyTOTP } from '@oslojs/otp' import { verifyTOTP } from '@oslojs/otp'
import { inject, injectable } from 'tsyringe' import { inject, injectable } from 'tsyringe'
@ -22,12 +21,11 @@ export class TotpService {
} }
async create(userId: string) { async create(userId: string) {
const twoFactorSecret = await new HMAC('SHA-1').generateKey() const secret = new Uint8Array(20)
try { try {
return await this.credentialsRepository.create({ return await this.credentialsRepository.create({
user_id: userId, user_id: userId,
secret_data: encodeHexLowerCase(twoFactorSecret), secret_data: encodeHexLowerCase(crypto.getRandomValues(secret)),
type: 'totp', type: 'totp',
}) })
} catch (e) { } catch (e) {

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as Card from '$components/ui/card' import * as Card from '$components/ui/card'
const { data } = $props() const { data } = $props()
let collections = data?.collections || [] let collections = data?.collections || []
</script> </script>
@ -10,13 +11,12 @@ let collections = data?.collections || []
<div class="container"> <div class="container">
<h1>Your Collections</h1> <h1>Your Collections</h1>
<div class="collection-list"> <div class="collection-list">
{#if collections.length === 0} {#if collections.length === 0}
<h2>You have no collections</h2> <h2>You have no collections</h2>
{:else} {:else}
{#each collections as collection} {#each collections as collection}
<Card.Root> <Card.Root class="shadow-sm hover:shadow-md transition-shadow duration-300 ease-in-out">
<Card.Header> <Card.Header>
<Card.Title>{collection.name}</Card.Title> <Card.Title>{collection.name}</Card.Title>
</Card.Header> </Card.Header>
@ -25,10 +25,6 @@ let collections = data?.collections || []
<p>Created at: {new Date(collection.createdAt).toLocaleString()}</p> <p>Created at: {new Date(collection.createdAt).toLocaleString()}</p>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
<!-- <div class="collection grid gap-0.5">
<h2><a href="/collections/{collection.cuid}">{collection.name}</a></h2>
<h3>Created at: {new Date(collection.createdAt).toLocaleString()}</h3>
</div> -->
{/each} {/each}
{/if} {/if}
</div> </div>

View file

@ -34,7 +34,12 @@ let { children } = $props()
.security-nav { .security-nav {
display: flex; display: flex;
@media (width <= 1000px) {
display: grid;
}
nav { nav {
@media (width > 1000px) {
width: 16rem; width: 16rem;
position: sticky; position: sticky;
top: 0; top: 0;
@ -43,6 +48,7 @@ let { children } = $props()
padding: 1rem; padding: 1rem;
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
height: 100vh; height: 100vh;
}
ul { ul {
list-style-type: none; list-style-type: none;

View file

@ -1,9 +1,9 @@
import { notSignedInMessage } from '$lib/flashMessages' import { notSignedInMessage } from '$lib/flashMessages'
import env from '$lib/server/api/common/env' import env from '$lib/server/api/common/env'
import { decodeHex, encodeBase32 } from '@oslojs/encoding'
import { createTOTPKeyURI } from '@oslojs/otp'
import { type Actions, fail } from '@sveltejs/kit' import { type Actions, fail } from '@sveltejs/kit'
import kebabCase from 'just-kebab-case' import kebabCase from 'just-kebab-case'
import { encodeBase32, decodeHex } from '@oslojs/encoding'
import { createTOTPKeyURI } from '@oslojs/otp'
import QRCode from 'qrcode' import QRCode from 'qrcode'
import { redirect } from 'sveltekit-flash-message/server' import { redirect } from 'sveltekit-flash-message/server'
import { zod } from 'sveltekit-superforms/adapters' import { zod } from 'sveltekit-superforms/adapters'
@ -63,7 +63,7 @@ export const load: PageServerLoad = async (event) => {
}) })
} }
const decodedHexSecret = decodeHex(createdTotpCredentials.secret_data) const decodedHexSecret = decodeHex(createdTotpCredentials.secret_data)
const secret = encodeBase32(new TextEncoder().encode(decodedHexSecret)) const secret = encodeBase32(decodedHexSecret)
const intervalInSeconds = 30 const intervalInSeconds = 30
const digits = 6 const digits = 6

View file

@ -1,7 +1,6 @@
import { notSignedInMessage } from '$lib/flashMessages.js' import { notSignedInMessage } from '$lib/flashMessages.js'
import { gamesTable, wishlist_items, wishlistsTable } from '$lib/server/api/databases/tables' import { gamesTable, wishlist_items, wishlistsTable } from '$lib/server/api/databases/tables'
import { db } from '$lib/server/api/packages/drizzle' import { db } from '$lib/server/api/packages/drizzle'
import { userNotAuthenticated } from '$lib/server/auth-utils'
import { modifyListGameSchema } from '$lib/validations/zod-schemas' import { modifyListGameSchema } from '$lib/validations/zod-schemas'
import { type Actions, error, fail } from '@sveltejs/kit' import { type Actions, error, fail } from '@sveltejs/kit'
import { and, eq } from 'drizzle-orm' import { and, eq } from 'drizzle-orm'
@ -17,15 +16,8 @@ export async function load(event) {
throw redirect(302, '/login', notSignedInMessage, event) throw redirect(302, '/login', notSignedInMessage, event)
} }
const userWishlists = await db.query.wishlists.findMany({ const { data } = await locals.api.wishlists.$get().then(locals.parseApiResponse)
columns: { const userWishlists = data?.wishlists
cuid: true,
name: true,
createdAt: true,
},
where: eq(wishlistsTable.user_id, authedUser.id),
})
console.log('wishlists', userWishlists)
if (userWishlists?.length === 0) { if (userWishlists?.length === 0) {
console.log('Wishlists not found') console.log('Wishlists not found')

View file

@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as Card from '$components/ui/card'
const { data } = $props() const { data } = $props()
const { wishlists = [] } = data const { wishlists = [] } = data
</script> </script>
@ -8,23 +10,27 @@ const { wishlists = [] } = data
</svelte:head> </svelte:head>
<div class="container"> <div class="container">
<h1>Your wishlistsTable</h1> <h1>Your wishlists</h1>
<div class="wishlists">
<div class="wishlist-list"> <div class="wishlist-list">
{#if wishlists.length === 0} {#if wishlists.length === 0}
<h2>You have no wishlistsTable</h2> <h2>You have no wishlists</h2>
{:else} {:else}
{#each wishlists as wishlist} {#each wishlists as wishlist}
<div class="collection grid gap-0.5"> <Card.Root class="shadow-sm hover:shadow-md transition-shadow duration-300 ease-in-out">
<h2><a href="/wishlists/{wishlist.cuid}">{wishlist.name}</a></h2> <a href="/wishlists/{wishlist.cuid}">
<h3>Created at: {new Date(wishlist.created_at).toLocaleString()}</h3> <Card.Header>
</div> <Card.Title>{wishlist.name}</Card.Title>
</Card.Header>
<Card.Content>
<h3>Created at: {new Date(wishlist.createdAt).toLocaleString()}</h3>
</Card.Content>
</a>
</Card.Root>
{/each} {/each}
{/if} {/if}
</div> </div>
</div> </div>
</div>
<style lang="postcss"> <style lang="postcss">
h1 { h1 {
@ -32,10 +38,6 @@ const { wishlists = [] } = data
width: 100%; width: 100%;
} }
.wishlists {
margin: 2rem 0;
}
.wishlist-list { .wishlist-list {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr)); grid-template-columns: repeat(3, minmax(200px, 1fr));

View file

@ -8,7 +8,7 @@ const welcomeName = $derived.by(() => {
welcomeName += data?.user?.firstName welcomeName += data?.user?.firstName
} }
if (data?.user?.lastName) { if (data?.user?.lastName) {
welcomeName += ' ' + data?.user?.lastName welcomeName = welcomeName.length === 0 ? data?.user?.lastName : welcomeName
} }
if (welcomeName.length === 0) { if (welcomeName.length === 0) {
@ -23,7 +23,7 @@ const welcomeName = $derived.by(() => {
{#if user} {#if user}
<h1>Welcome, {welcomeName}!</h1> <h1>Welcome, {welcomeName}!</h1>
<div> <div>
<h2>You wishlistsTable:</h2> <h2>You wishlists:</h2>
{#each wishlists as wishlist} {#each wishlists as wishlist}
<a href="/wishlists/{wishlist.cuid}">{wishlist.name}</a> <a href="/wishlists/{wishlist.cuid}">{wishlist.name}</a>
{/each} {/each}