Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Last active July 19, 2023 17:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kentcdodds/4cbf74cfb232f798e71603d0deb91f4d to your computer and use it in GitHub Desktop.
Save kentcdodds/4cbf74cfb232f798e71603d0deb91f4d to your computer and use it in GitHub Desktop.
Big git diff
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/root.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/root.tsx
index 7dee21b..6c3aa4b 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/root.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/root.tsx
@@ -104,7 +104,6 @@ export const meta: V2_MetaFunction = () => {
{ name: 'description', content: `Your own captain's log` },
]
}
-// change
export function ErrorBoundary() {
return (
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/resources+/images.$imageId.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/resources+/images.$imageId.tsx
index f9c0cfa..0aadfa7 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/resources+/images.$imageId.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/resources+/images.$imageId.tsx
@@ -1,28 +1,20 @@
-import { Response, type DataFunctionArgs } from '@remix-run/node'
-import fs from 'node:fs'
-import { PassThrough } from 'node:stream'
-import { db } from '~/utils/db.server.ts'
+import { type DataFunctionArgs } from '@remix-run/node'
+import { prisma } from '~/utils/db.server.ts'
import { invariantResponse } from '~/utils/misc.ts'
export async function loader({ params }: DataFunctionArgs) {
- invariantResponse(params.imageId, 'Invalid image ID')
- const image = db.image.findFirst({
- where: { id: { equals: params.imageId } },
+ invariantResponse(params.imageId, 'Image ID is required', { status: 400 })
+ const image = await prisma.image.findUnique({
+ where: { id: params.imageId },
+ select: { contentType: true, file: { select: { blob: true } } },
})
- invariantResponse(image, 'Image not found', { status: 404 })
- const { filepath, contentType } = image
- const body = new PassThrough()
- const stream = fs.createReadStream(filepath)
- stream.on('open', () => stream.pipe(body))
- stream.on('error', err => body.end(err))
- stream.on('end', () => body.end())
- return new Response(body, {
- status: 200,
+ invariantResponse(image?.file, 'Not found', { status: 404 })
+
+ return new Response(image.file.blob, {
headers: {
- 'Content-Type': contentType,
- 'Content-Disposition': `inline; filename="${params.imageId}"`,
- 'Cache-Control': 'public, max-age=31536000, immutable',
+ 'Content-Type': image.contentType,
+ 'Cache-Control': 'max-age=31536000',
},
})
}
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username.tsx
index 3f2d8ad..393e098 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username.tsx
@@ -1,32 +1,69 @@
import { json, type DataFunctionArgs } from '@remix-run/node'
import { Link, useLoaderData, type V2_MetaFunction } from '@remix-run/react'
import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'
-import { db } from '~/utils/db.server.ts'
+import { Spacer } from '~/components/spacer.tsx'
+import { Button } from '~/components/ui/button.tsx'
+import { prisma } from '~/utils/db.server.ts'
+import { getUserImgSrc } from '~/utils/misc.ts'
export async function loader({ params }: DataFunctionArgs) {
- const user = db.user.findFirst({
+ const user = await prisma.user.findFirst({
+ select: {
+ name: true,
+ username: true,
+ createdAt: true,
+ image: { select: { id: true } },
+ },
where: {
- username: {
- equals: params.username,
- },
+ username: params.username,
},
})
if (!user) {
throw new Response('User not found', { status: 404 })
}
- return json({
- user: { name: user.name, username: user.username },
- })
+ return json({ user, userJoinedDisplay: user.createdAt.toLocaleDateString() })
}
export default function ProfileRoute() {
const data = useLoaderData<typeof loader>()
+ const user = data.user
+ const userDisplayName = user.name ?? user.username
+
return (
- <div className="container mb-48 mt-36">
- <h1 className="text-h1">{data.user.name ?? data.user.username}</h1>
- <Link to="notes" className="underline" prefetch="intent">
- Notes
- </Link>
+ <div className="container mb-48 mt-36 flex flex-col items-center justify-center">
+ <Spacer size="4xs" />
+
+ <div className="container flex flex-col items-center rounded-3xl bg-muted p-12">
+ <div className="relative w-52">
+ <div className="absolute -top-40">
+ <div className="relative">
+ <img
+ src={getUserImgSrc(data.user.image?.id)}
+ alt={userDisplayName}
+ className="h-52 w-52 rounded-full object-cover"
+ />
+ </div>
+ </div>
+ </div>
+
+ <Spacer size="sm" />
+
+ <div className="flex flex-col items-center">
+ <div className="flex flex-wrap items-center justify-center gap-4">
+ <h1 className="text-center text-h2">{userDisplayName}</h1>
+ </div>
+ <p className="mt-2 text-center text-muted-foreground">
+ Joined {data.userJoinedDisplay}
+ </p>
+ <div className="mt-10 flex gap-4">
+ <Button asChild>
+ <Link to="notes" prefetch="intent">
+ {userDisplayName}'s notes
+ </Link>
+ </Button>
+ </div>
+ </div>
+ </div>
</div>
)
}
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId.tsx
index 10d72aa..0dad15b 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId.tsx
@@ -7,29 +7,26 @@ import {
} from '@remix-run/react'
import { floatingToolbarClassName } from '~/components/floating-toolbar.tsx'
import { Button } from '~/components/ui/button.tsx'
-import { db } from '~/utils/db.server.ts'
+import { prisma } from '~/utils/db.server.ts'
import { invariantResponse } from '~/utils/misc.ts'
import { type loader as notesLoader } from './notes.tsx'
import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'
export async function loader({ params }: DataFunctionArgs) {
- const note = db.note.findFirst({
- where: {
- id: {
- equals: params.noteId,
+ const note = await prisma.note.findFirst({
+ select: {
+ title: true,
+ content: true,
+ images: {
+ select: { id: true, altText: true },
},
},
+ where: { id: params.noteId },
})
- if (!note) {
- throw new Response('Note note found', { status: 404 })
- }
- return json({
- note: {
- title: note.title,
- content: note.content,
- images: note.images.map(i => ({ id: i.id, altText: i.altText })),
- },
- })
+
+ invariantResponse(note, 'Note not found', { status: 404 })
+
+ return json({ note })
}
export async function action({ request, params }: DataFunctionArgs) {
@@ -40,7 +37,7 @@ export async function action({ request, params }: DataFunctionArgs) {
invariantResponse(intent === 'delete', 'Invalid intent')
- db.note.delete({ where: { id: { equals: params.noteId } } })
+ await prisma.note.delete({ where: { id: params.noteId } })
return redirect(`/users/${params.username}/notes`)
}
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId_.edit.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId_.edit.tsx
index e1d65de..895b177 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId_.edit.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.$noteId_.edit.tsx
@@ -15,36 +15,35 @@ import {
type DataFunctionArgs,
} from '@remix-run/node'
import { Form, useActionData, useLoaderData } from '@remix-run/react'
+import cuid from 'cuid'
import { useRef, useState } from 'react'
import { z } from 'zod'
import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'
import { floatingToolbarClassName } from '~/components/floating-toolbar.tsx'
+import { ErrorList, Field, TextareaField } from '~/components/forms.tsx'
import { Button } from '~/components/ui/button.tsx'
-import { Input } from '~/components/ui/input.tsx'
+import { Icon } from '~/components/ui/icon.tsx'
import { Label } from '~/components/ui/label.tsx'
import { StatusButton } from '~/components/ui/status-button.tsx'
import { Textarea } from '~/components/ui/textarea.tsx'
-import { db, updateNote } from '~/utils/db.server.ts'
+import { prisma } from '~/utils/db.server.ts'
import { cn, invariantResponse, useIsSubmitting } from '~/utils/misc.ts'
export async function loader({ params }: DataFunctionArgs) {
- const note = db.note.findFirst({
- where: {
- id: {
- equals: params.noteId,
+ const note = await prisma.note.findFirst({
+ where: { id: params.noteId },
+ select: {
+ title: true,
+ content: true,
+ images: {
+ select: { id: true, altText: true },
},
},
})
- if (!note) {
- throw new Response('Note note found', { status: 404 })
- }
- return json({
- note: {
- title: note.title,
- content: note.content,
- images: note.images.map(i => ({ id: i.id, altText: i.altText })),
- },
- })
+
+ invariantResponse(note, 'Note not found', { status: 404 })
+
+ return json({ note })
}
const titleMinLength = 1
@@ -88,31 +87,67 @@ export async function action({ request, params }: DataFunctionArgs) {
if (!submission.value) {
return json({ status: 'error', submission } as const, { status: 400 })
}
- const { title, content, images } = submission.value
- await updateNote({ id: params.noteId, title, content, images })
- return redirect(`/users/${params.username}/notes/${params.noteId}`)
-}
+ const { title, content, images = [] } = submission.value
-function ErrorList({
- id,
- errors,
-}: {
- id?: string
- errors?: Array<string> | string | null
-}) {
- if (!errors) return null
- errors = Array.isArray(errors) ? errors : [errors]
+ await prisma.$transaction(async p => {
+ const updatedImages = await Promise.all(
+ images.map(async image => {
+ if (image.file.size > 0) {
+ console.log('file has been uploaded')
+ const blob = Buffer.from(await image.file.arrayBuffer())
+ if (image.id) {
+ const newId = cuid()
+ console.log('replacing existing image', image.id, newId)
+ const replaced = await p.image.update({
+ select: { id: true },
+ where: { id: image.id },
+ data: {
+ id: newId,
+ altText: image.altText,
+ file: { update: { blob: blob } },
+ },
+ })
+ console.log({ replaced })
+ return { id: newId }
+ } else {
+ console.log('creating new image')
+ const i = await p.image.create({
+ select: { id: true },
+ data: {
+ altText: image.altText,
+ contentType: image.file.type,
+ file: { create: { blob } },
+ },
+ })
+ return i
+ }
+ } else if (image.id) {
+ console.log('updating existing image')
+ return await p.image.update({
+ select: { id: true },
+ where: { id: image.id },
+ data: { altText: image.altText },
+ })
+ }
+ }),
+ )
+
+ console.log('updatedImages', updatedImages)
+
+ await p.note.update({
+ where: { id: params.noteId },
+ data: {
+ title,
+ content,
+ images: {
+ set: updatedImages.filter(Boolean),
+ },
+ },
+ })
+ })
- return errors.length ? (
- <ul id={id} className="flex flex-col gap-1">
- {errors.map((error, i) => (
- <li key={i} className="text-[10px] text-foreground-danger">
- {error}
- </li>
- ))}
- </ul>
- ) : null
+ return redirect(`/users/${params.username}/notes/${params.noteId}`)
}
export default function NoteEdit() {
@@ -150,31 +185,21 @@ export default function NoteEdit() {
*/}
<button type="submit" className="hidden" />
<div className="flex flex-col gap-1">
- <div>
- <Label htmlFor={fields.title.id}>Title</Label>
- <Input
- autoFocus
- {...conform.input(fields.title, { ariaAttributes: true })}
- />
- <div className="min-h-[32px] px-4 pb-3 pt-1">
- <ErrorList
- id={fields.title.errorId}
- errors={fields.title.errors}
- />
- </div>
- </div>
- <div>
- <Label htmlFor={fields.content.id}>Content</Label>
- <Textarea
- {...conform.textarea(fields.content, { ariaAttributes: true })}
- />
- <div className="min-h-[32px] px-4 pb-3 pt-1">
- <ErrorList
- id={fields.content.errorId}
- errors={fields.content.errors}
- />
- </div>
- </div>
+ <Field
+ labelProps={{ children: 'Title' }}
+ inputProps={{
+ autoFocus: true,
+ ...conform.input(fields.title, { ariaAttributes: true }),
+ }}
+ errors={fields.title.errors}
+ />
+ <TextareaField
+ labelProps={{ children: 'Content' }}
+ textareaProps={{
+ ...conform.textarea(fields.content, { ariaAttributes: true }),
+ }}
+ errors={fields.content.errors}
+ />
<div>
<Label>Images</Label>
<ul className="flex flex-col gap-4">
@@ -187,7 +212,9 @@ export default function NoteEdit() {
className="absolute right-0 top-0 text-destructive"
{...list.remove(fields.images.name, { index })}
>
- <span aria-hidden>❌</span>{' '}
+ <span aria-hidden>
+ <Icon name="cross-1" />
+ </span>{' '}
<span className="sr-only">Remove image {index + 1}</span>
</button>
<ImageChooser config={image} />
@@ -199,7 +226,9 @@ export default function NoteEdit() {
className="mt-3"
{...list.append(fields.images.name, { defaultValue: {} })}
>
- <span aria-hidden>➕ Image</span>{' '}
+ <span aria-hidden>
+ <Icon name="plus">Image</Icon>
+ </span>{' '}
<span className="sr-only">Add image</span>
</Button>
</div>
@@ -267,7 +296,7 @@ function ImageChooser({
</div>
) : (
<div className="flex h-32 w-32 items-center justify-center rounded-lg border border-muted-foreground text-4xl text-muted-foreground">
- ➕
+ <Icon name="plus" />
</div>
)}
{existingImage ? (
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.tsx
index 7842ab9..e187c76 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/routes/users+/$username_+/notes.tsx
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/routes/users+/$username_+/notes.tsx
@@ -1,31 +1,25 @@
import { json, type DataFunctionArgs } from '@remix-run/node'
import { Link, NavLink, Outlet, useLoaderData } from '@remix-run/react'
import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'
-import { db } from '~/utils/db.server.ts'
-import { cn } from '~/utils/misc.ts'
+import { prisma } from '~/utils/db.server.ts'
+import { cn, getUserImgSrc } from '~/utils/misc.ts'
export async function loader({ params }: DataFunctionArgs) {
- const owner = db.user.findFirst({
- where: {
- username: {
- equals: params.username,
- },
- },
+ const owner = await prisma.user.findFirst({
+ select: { name: true, username: true, image: { select: { id: true } } },
+ where: { username: params.username },
})
if (!owner) {
throw new Response('Owner not found', { status: 404 })
}
- const notes = db.note
- .findMany({
- where: {
- owner: {
- username: {
- equals: params.username,
- },
- },
+ const notes = await prisma.note.findMany({
+ select: { id: true, title: true },
+ where: {
+ owner: {
+ username: params.username,
},
- })
- .map(({ id, title }) => ({ id, title }))
+ },
+ })
return json({ owner, notes })
}
@@ -41,9 +35,14 @@ export default function NotesRoute() {
<div className="absolute inset-0 flex flex-col">
<Link
to={`/users/${data.owner.username}`}
- className="pb-4 pl-8 pr-4 pt-12"
+ className="flex flex-col items-center justify-center gap-2 bg-muted pb-4 pl-8 pr-4 pt-12 lg:flex-row lg:justify-start lg:gap-4"
>
- <h1 className="text-base font-bold md:text-lg lg:text-left lg:text-2xl">
+ <img
+ src={getUserImgSrc(data.owner.image?.id)}
+ alt={ownerDisplayName}
+ className="h-16 w-16 rounded-full object-cover lg:h-24 lg:w-24"
+ />
+ <h1 className="text-center text-base font-bold md:text-lg lg:text-left lg:text-2xl">
{ownerDisplayName}'s Notes
</h1>
</Link>
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/utils/db.server.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/utils/db.server.ts
index 7d3dfc1..6b73e74 100644
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/app/utils/db.server.ts
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/app/utils/db.server.ts
@@ -1,231 +1,10 @@
-/**
- * Don't worry too much about this file. It's just an in-memory "database"
- * for the purposes of our workshop. The data modeling workshop will cover
- * the proper database.
- */
-import { factory, manyOf, nullable, oneOf, primaryKey } from '@mswjs/data'
-import path from 'node:path'
-import fs from 'node:fs/promises'
-import os from 'node:os'
-import crypto from 'crypto'
+import { PrismaClient } from '@prisma/client'
import { singleton } from './singleton.server.ts'
-const getId = () => crypto.randomBytes(16).toString('hex').slice(0, 8)
-
-/*
-
-model File {
- id String @id @unique @default(cuid())
- blob Bytes
-
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- image Image?
-}
-
-model Image {
- file File @relation(fields: [fileId], references: [id], onDelete: Cascade, onUpdate: Cascade)
- fileId String @unique
-
- contentType String
- altText String?
-
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
-
- user User?
-}
-*/
-
-export const db = singleton('db', () => {
- const db = factory({
- user: {
- id: primaryKey(getId),
- email: String,
- username: String,
- name: nullable(String),
-
- createdAt: () => new Date(),
-
- notes: manyOf('note'),
- },
- note: {
- id: primaryKey(getId),
- title: String,
- content: String,
-
- createdAt: () => new Date(),
-
- owner: oneOf('user'),
- images: manyOf('image'),
- },
- image: {
- id: primaryKey(getId),
- filepath: String,
- contentType: String,
- altText: nullable(String),
- },
- })
-
- const kody = db.user.create({
- id: '9d6eba59daa2fc2078cf8205cd451041',
- email: 'kody@kcd.dev',
- username: 'kody',
- name: 'Kody',
- })
-
- const kodyNotes = [
- {
- id: 'd27a197e',
- title: 'Basic Koala Facts',
- content:
- 'Koalas are found in the eucalyptus forests of eastern Australia. They have grey fur with a cream-coloured chest, and strong, clawed feet, perfect for living in the branches of trees!',
- },
- {
- id: '414f0c09',
- title: 'Koalas like to cuddle',
- content:
- 'Cuddly critters, koalas measure about 60cm to 85cm long, and weigh about 14kg.',
- },
- {
- id: '260366b1',
- title: 'Not bears',
- content:
- "Although you may have heard people call them koala 'bears', these awesome animals aren’t bears at all – they are in fact marsupials. A group of mammals, most marsupials have pouches where their newborns develop.",
- },
- {
- id: 'bb79cf45',
- title: 'Snowboarding Adventure',
- content:
- "Today was an epic day on the slopes! Shredded fresh powder with my friends, caught some sick air, and even attempted a backflip. Can't wait for the next snowy adventure!",
- },
- {
- id: '9f4308be',
- title: 'Onewheel Tricks',
- content:
- "Mastered a new trick on my Onewheel today called '180 Spin'. It's exhilarating to carve through the streets while pulling off these rad moves. Time to level up and learn more!",
- },
- {
- id: '306021fb',
- title: 'Coding Dilemma',
- content:
- "Stuck on a bug in my latest coding project. Need to figure out why my function isn't returning the expected output. Time to dig deep, debug, and conquer this challenge!",
- },
- {
- id: '16d4912a',
- title: 'Coding Mentorship',
- content:
- "Had a fantastic coding mentoring session today with Sarah. Helped her understand the concept of recursion, and she made great progress. It's incredibly fulfilling to help others improve their coding skills.",
- },
- {
- id: '3199199e',
- title: 'Koala Fun Facts',
- content:
- "Did you know that koalas sleep for up to 20 hours a day? It's because their diet of eucalyptus leaves doesn't provide much energy. But when I'm awake, I enjoy munching on leaves, chilling in trees, and being the cuddliest koala around!",
- },
- {
- id: '2030ffd3',
- title: 'Skiing Adventure',
- content:
- 'Spent the day hitting the slopes on my skis. The fresh powder made for some incredible runs and breathtaking views. Skiing down the mountain at top speed is an adrenaline rush like no other!',
- },
- {
- id: 'f375a804',
- title: 'Code Jam Success',
- content:
- 'Participated in a coding competition today and secured the first place! The adrenaline, the challenging problems, and the satisfaction of finding optimal solutions—it was an amazing experience. Feeling proud and motivated to keep pushing my coding skills further!',
- },
- {
- id: '562c541b',
- title: 'Koala Conservation Efforts',
- content:
- "Joined a local conservation group to protect koalas and their habitats. Together, we're planting more eucalyptus trees, raising awareness about their endangered status, and working towards a sustainable future for these adorable creatures. Every small step counts!",
- },
- // extra long note to test scrolling
- {
- id: 'f67ca40b',
- title: 'Game day',
- content:
- "Just got back from the most amazing game. I've been playing soccer for a long time, but I've not once scored a goal. Well, today all that changed! I finally scored my first ever goal.\n\nI'm in an indoor league, and my team's not the best, but we're pretty good and I have fun, that's all that really matters. Anyway, I found myself at the other end of the field with the ball. It was just me and the goalie. I normally just kick the ball and hope it goes in, but the ball was already rolling toward the goal. The goalie was about to get the ball, so I had to charge. I managed to get possession of the ball just before the goalie got it. I brought it around the goalie and had a perfect shot. I screamed so loud in excitement. After all these years playing, I finally scored a goal!\n\nI know it's not a lot for most folks, but it meant a lot to me. We did end up winning the game by one. It makes me feel great that I had a part to play in that.\n\nIn this team, I'm the captain. I'm constantly cheering my team on. Even after getting injured, I continued to come and watch from the side-lines. I enjoy yelling (encouragingly) at my team mates and helping them be the best they can. I'm definitely not the best player by a long stretch. But I really enjoy the game. It's a great way to get exercise and have good social interactions once a week.\n\nThat said, it can be hard to keep people coming and paying dues and stuff. If people don't show up it can be really hard to find subs. I have a list of people I can text, but sometimes I can't find anyone.\n\nBut yeah, today was awesome. I felt like more than just a player that gets in the way of the opposition, but an actual asset to the team. Really great feeling.\n\nAnyway, I'm rambling at this point and really this is just so we can have a note that's pretty long to test things out. I think it's long enough now... Cheers!",
- },
- ]
-
- for (const note of kodyNotes) {
- db.note.create({
- ...note,
- owner: kody,
- })
- }
-
- return db
+const prisma = singleton('prisma', () => {
+ const p = new PrismaClient()
+ p.$connect()
+ return p
})
-export async function updateNote({
- id,
- title,
- content,
- images,
-}: {
- id: string
- title: string
- content: string
- images?: Array<{
- id?: string
- file?: File
- altText?: string
- } | null>
-}) {
- const noteImagePromises =
- images?.map(async image => {
- if (!image) return null
-
- if (image.id) {
- const hasReplacement = (image?.file?.size || 0) > 0
- const filepath =
- image.file && hasReplacement
- ? await writeImage(image.file)
- : undefined
- // update the ID so caching is invalidated
- const id = image.file && hasReplacement ? getId() : image.id
-
- return db.image.update({
- where: { id: { equals: image.id } },
- data: {
- id,
- filepath,
- altText: image.altText,
- },
- })
- } else if (image.file) {
- if (image.file.size < 1) return null
- const filepath = await writeImage(image.file)
- return db.image.create({
- altText: image.altText,
- filepath,
- contentType: image.file.type,
- })
- } else {
- return null
- }
- }) ?? []
-
- const noteImages = await Promise.all(noteImagePromises)
- db.note.update({
- where: { id: { equals: id } },
- data: {
- title,
- content,
- images: noteImages.filter(Boolean),
- },
- })
-}
-
-async function writeImage(image: File) {
- const tmpDir = path.join(os.tmpdir(), 'epic-web', 'images')
- await fs.mkdir(tmpDir, { recursive: true })
-
- const timestamp = Date.now()
- const filepath = path.join(tmpDir, `${timestamp}.${image.name}`)
- await fs.writeFile(filepath, Buffer.from(await image.arrayBuffer()))
- return filepath
-}
+export { prisma }
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/data.db var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/data.db
new file mode 100644
index 0000000..3f9001f
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/data.db differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/20230719033237_init/migration.sql var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/20230719033237_init/migration.sql
new file mode 100644
index 0000000..1138ac9
--- /dev/null
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/20230719033237_init/migration.sql
@@ -0,0 +1,70 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "email" TEXT NOT NULL,
+ "username" TEXT NOT NULL,
+ "name" TEXT,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL
+);
+
+-- CreateTable
+CREATE TABLE "Image" (
+ "id" TEXT NOT NULL,
+ "contentType" TEXT NOT NULL,
+ "altText" TEXT,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ "noteId" TEXT,
+ "userId" TEXT,
+ CONSTRAINT "Image_noteId_fkey" FOREIGN KEY ("noteId") REFERENCES "Note" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT "Image_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+-- CreateTable
+CREATE TABLE "File" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "blob" BLOB NOT NULL,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ "imageId" TEXT NOT NULL,
+ CONSTRAINT "File_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+-- CreateTable
+CREATE TABLE "Note" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "title" TEXT NOT NULL,
+ "content" TEXT NOT NULL,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ "ownerId" TEXT NOT NULL,
+ CONSTRAINT "Note_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_id_key" ON "User"("id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Image_id_key" ON "Image"("id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Image_userId_key" ON "Image"("userId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "File_id_key" ON "File"("id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "File_imageId_key" ON "File"("imageId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Note_id_key" ON "Note"("id");
+
+-- CreateIndex
+CREATE INDEX "Note_ownerId_updatedAt_idx" ON "Note"("ownerId", "updatedAt" DESC);
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/migration_lock.toml var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..e5e5c47
--- /dev/null
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/schema.prisma var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/schema.prisma
new file mode 100644
index 0000000..854842c
--- /dev/null
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/schema.prisma
@@ -0,0 +1,66 @@
+datasource db {
+ provider = "sqlite"
+ url = env("DATABASE_URL")
+}
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+model User {
+ id String @id @unique @default(cuid())
+ email String @unique
+ username String @unique
+ name String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ image Image?
+ notes Note[]
+}
+
+model Image {
+ id String @unique @default(cuid())
+
+ contentType String
+ altText String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ note Note? @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ noteId String?
+
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ userId String? @unique
+
+ file File?
+}
+
+model File {
+ id String @id @unique @default(cuid())
+ blob Bytes
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ image Image @relation(fields: [imageId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ imageId String @unique
+}
+
+model Note {
+ id String @id @unique @default(cuid())
+ title String
+ content String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+ ownerId String
+
+ images Image[]
+
+ // our user search orders by recently owners who have recently updated notes
+ // so indexing on the owner ID helps with that (a LOT). (Really!)
+ @@index([ownerId, updatedAt(sort: Desc)])
+}
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/seed.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/seed.ts
new file mode 100644
index 0000000..c67ddd9
--- /dev/null
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/prisma/seed.ts
@@ -0,0 +1,278 @@
+import fs from 'fs'
+import { faker } from '@faker-js/faker'
+import { createUser } from 'tests/db-utils.ts'
+import { prisma } from '~/utils/db.server.ts'
+
+const altTexts = [
+ 'a nice country house',
+ 'a city scape',
+ 'a sunrise',
+ 'a group of friends',
+ 'friends being inclusive of someone who looks lonely',
+ 'an illustration of a hot air balloon',
+ 'an office full of laptops and other office equipment that look like it was abandond in a rush out of the building in an emergency years ago.',
+ 'a rusty lock',
+ 'something very happy in nature',
+ `someone at the end of a cry session who's starting to feel a little better.`,
+]
+
+async function seed() {
+ console.log('🌱 Seeding...')
+ console.time(`🌱 Database has been seeded`)
+
+ console.time('🧹 Cleaned up the database...')
+ await prisma.user.deleteMany()
+ await prisma.image.deleteMany()
+ console.timeEnd('🧹 Cleaned up the database...')
+
+ const totalUsers = 20
+ console.time(`👤 Created ${totalUsers} users...`)
+ await Promise.all(
+ Array.from({ length: totalUsers }, async (_, index) => {
+ const userData = createUser()
+ const userCreatedUpdated = getCreatedAndUpdated()
+ const user = await prisma.user.create({
+ select: { id: true },
+ data: {
+ ...userData,
+ ...userCreatedUpdated,
+ image: {
+ create: await img({
+ filepath: `./tests/fixtures/images/user/${index % 10}.jpg`,
+ }),
+ },
+ notes: {
+ create: await Promise.all(
+ Array.from({
+ length: faker.number.int({ min: 0, max: 3 }),
+ }).map(async () => ({
+ title: faker.lorem.sentence(),
+ content: faker.lorem.paragraphs(),
+ ...getCreatedAndUpdated(userCreatedUpdated.createdAt),
+ images: {
+ create: await Promise.all(
+ Array.from({
+ length: faker.number.int({ min: 0, max: 5 }),
+ }).map(async () => {
+ const imgNumber = faker.number.int({ min: 0, max: 9 })
+ return img({
+ altText: altTexts[imgNumber],
+ filepath: `./tests/fixtures/images/notes/${imgNumber}.png`,
+ })
+ }),
+ ),
+ },
+ })),
+ ),
+ },
+ },
+ })
+ return user
+ }),
+ ).then(users => users.filter(Boolean))
+ console.timeEnd(`👤 Created ${totalUsers} users...`)
+
+ console.time(`🐨 Created user "kody"`)
+ await prisma.user.create({
+ data: {
+ email: 'kody@kcd.dev',
+ username: 'kody',
+ name: 'Kody',
+ image: {
+ create: await img({
+ filepath: './tests/fixtures/images/user/kody.png',
+ }),
+ },
+ notes: {
+ create: [
+ {
+ id: 'd27a197e',
+ title: 'Basic Koala Facts',
+ content:
+ 'Koalas are found in the eucalyptus forests of eastern Australia. They have grey fur with a cream-coloured chest, and strong, clawed feet, perfect for living in the branches of trees!',
+ images: {
+ create: [
+ await img({
+ altText: 'an adorable koala cartoon illustration',
+ filepath: './tests/fixtures/images/kody-notes/cute-koala.png',
+ }),
+ await img({
+ altText: 'a cartoon illustration of a koala in a tree eating',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-eating.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '414f0c09',
+ title: 'Koalas like to cuddle',
+ content:
+ 'Cuddly critters, koalas measure about 60cm to 85cm long, and weigh about 14kg.',
+ images: {
+ create: [
+ await img({
+ altText: 'a cartoon illustration of koalas cuddling',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-cuddle.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '260366b1',
+ title: 'Not bears',
+ content:
+ "Although you may have heard people call them koala 'bears', these awesome animals aren’t bears at all – they are in fact marsupials. A group of mammals, most marsupials have pouches where their newborns develop.",
+ },
+ {
+ id: 'bb79cf45',
+ title: 'Snowboarding Adventure',
+ content:
+ "Today was an epic day on the slopes! Shredded fresh powder with my friends, caught some sick air, and even attempted a backflip. Can't wait for the next snowy adventure!",
+ images: {
+ create: [
+ await img({
+ altText: 'a beautiful mountain covered in snow',
+ filepath: './tests/fixtures/images/kody-notes/mountain.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '9f4308be',
+ title: 'Onewheel Tricks',
+ content:
+ "Mastered a new trick on my Onewheel today called '180 Spin'. It's exhilarating to carve through the streets while pulling off these rad moves. Time to level up and learn more!",
+ },
+ {
+ id: '306021fb',
+ title: 'Coding Dilemma',
+ content:
+ "Stuck on a bug in my latest coding project. Need to figure out why my function isn't returning the expected output. Time to dig deep, debug, and conquer this challenge!",
+ images: {
+ create: [
+ await img({
+ altText: 'a koala coding at the computer',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-coder.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '16d4912a',
+ title: 'Coding Mentorship',
+ content:
+ "Had a fantastic coding mentoring session today with Sarah. Helped her understand the concept of recursion, and she made great progress. It's incredibly fulfilling to help others improve their coding skills.",
+ images: {
+ create: [
+ await img({
+ altText:
+ 'a koala in a friendly and helpful posture. The Koala is standing next to and teaching a woman who is coding on a computer and shows positive signs of learning and understanding what is being explained.',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-mentor.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '3199199e',
+ title: 'Koala Fun Facts',
+ content:
+ "Did you know that koalas sleep for up to 20 hours a day? It's because their diet of eucalyptus leaves doesn't provide much energy. But when I'm awake, I enjoy munching on leaves, chilling in trees, and being the cuddliest koala around!",
+ },
+ {
+ id: '2030ffd3',
+ title: 'Skiing Adventure',
+ content:
+ 'Spent the day hitting the slopes on my skis. The fresh powder made for some incredible runs and breathtaking views. Skiing down the mountain at top speed is an adrenaline rush like no other!',
+ images: {
+ create: [
+ await img({
+ altText: 'a beautiful mountain covered in snow',
+ filepath: './tests/fixtures/images/kody-notes/mountain.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: 'f375a804',
+ title: 'Code Jam Success',
+ content:
+ 'Participated in a coding competition today and secured the first place! The adrenaline, the challenging problems, and the satisfaction of finding optimal solutions—it was an amazing experience. Feeling proud and motivated to keep pushing my coding skills further!',
+ images: {
+ create: [
+ await img({
+ altText: 'a koala coding at the computer',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-coder.png',
+ }),
+ ],
+ },
+ },
+ {
+ id: '562c541b',
+ title: 'Koala Conservation Efforts',
+ content:
+ "Joined a local conservation group to protect koalas and their habitats. Together, we're planting more eucalyptus trees, raising awareness about their endangered status, and working towards a sustainable future for these adorable creatures. Every small step counts!",
+ },
+ // extra long note to test scrolling
+ {
+ id: 'f67ca40b',
+ title: 'Game day',
+ content:
+ "Just got back from the most amazing game. I've been playing soccer for a long time, but I've not once scored a goal. Well, today all that changed! I finally scored my first ever goal.\n\nI'm in an indoor league, and my team's not the best, but we're pretty good and I have fun, that's all that really matters. Anyway, I found myself at the other end of the field with the ball. It was just me and the goalie. I normally just kick the ball and hope it goes in, but the ball was already rolling toward the goal. The goalie was about to get the ball, so I had to charge. I managed to get possession of the ball just before the goalie got it. I brought it around the goalie and had a perfect shot. I screamed so loud in excitement. After all these years playing, I finally scored a goal!\n\nI know it's not a lot for most folks, but it meant a lot to me. We did end up winning the game by one. It makes me feel great that I had a part to play in that.\n\nIn this team, I'm the captain. I'm constantly cheering my team on. Even after getting injured, I continued to come and watch from the side-lines. I enjoy yelling (encouragingly) at my team mates and helping them be the best they can. I'm definitely not the best player by a long stretch. But I really enjoy the game. It's a great way to get exercise and have good social interactions once a week.\n\nThat said, it can be hard to keep people coming and paying dues and stuff. If people don't show up it can be really hard to find subs. I have a list of people I can text, but sometimes I can't find anyone.\n\nBut yeah, today was awesome. I felt like more than just a player that gets in the way of the opposition, but an actual asset to the team. Really great feeling.\n\nAnyway, I'm rambling at this point and really this is just so we can have a note that's pretty long to test things out. I think it's long enough now... Cheers!",
+ images: {
+ create: [
+ await img({
+ altText:
+ 'a cute cartoon koala kicking a soccer ball on a soccer field ',
+ filepath:
+ './tests/fixtures/images/kody-notes/koala-soccer.png',
+ }),
+ ],
+ },
+ },
+ ],
+ },
+ },
+ })
+ console.timeEnd(`🐨 Created user "kody"`)
+
+ console.timeEnd(`🌱 Database has been seeded`)
+}
+
+async function img({
+ altText,
+ filepath,
+}: {
+ altText?: string
+ filepath: string
+}) {
+ const contentType = filepath.endsWith('.png') ? 'image/png' : 'image/jpeg'
+ return {
+ contentType,
+ altText,
+ file: {
+ create: {
+ blob: await fs.promises.readFile(filepath),
+ },
+ },
+ }
+}
+
+function getCreatedAndUpdated(from: Date = new Date(2020, 0, 1)) {
+ const createdAt = faker.date.between({ from, to: new Date() })
+ const updatedAt = faker.date.between({ from: createdAt, to: new Date() })
+ return { createdAt, updatedAt }
+}
+
+seed()
+ .catch(e => {
+ console.error(e)
+ process.exit(1)
+ })
+ .finally(async () => {
+ await prisma.$disconnect()
+ })
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/prisma/stuff.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/prisma/stuff.ts
deleted file mode 100644
index e789c9c..0000000
--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__01.init__sep__01.problem.named/39yuovg9r3j/prisma/stuff.ts
+++ /dev/null
@@ -1 +0,0 @@
-console.log('hi') // yo
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/db-utils.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/db-utils.ts
new file mode 100644
index 0000000..40604f1
--- /dev/null
+++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/db-utils.ts
@@ -0,0 +1,29 @@
+import { faker } from '@faker-js/faker'
+import { UniqueEnforcer } from 'enforce-unique'
+
+const uniqueUsernameEnforcer = new UniqueEnforcer()
+
+export function createUser() {
+ const firstName = faker.person.firstName()
+ const lastName = faker.person.lastName()
+
+ const username = uniqueUsernameEnforcer
+ .enforce(() => {
+ return (
+ faker.string.alphanumeric({ length: 5 }) +
+ ' ' +
+ faker.internet.userName({
+ firstName: firstName.toLowerCase(),
+ lastName: lastName.toLowerCase(),
+ })
+ )
+ })
+ .slice(0, 20)
+ .toLowerCase()
+ .replace(/[^a-z0-9_]/g, '_')
+ return {
+ username,
+ name: `${firstName} ${lastName}`,
+ email: `${username}@example.com`,
+ }
+}
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/cute-koala.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/cute-koala.png
new file mode 100644
index 0000000..b322eb5
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/cute-koala.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-coder.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-coder.png
new file mode 100644
index 0000000..63e6d59
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-coder.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-cuddle.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-cuddle.png
new file mode 100644
index 0000000..ccc1d3b
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-cuddle.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-eating.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-eating.png
new file mode 100644
index 0000000..00713e8
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-eating.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-mentor.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-mentor.png
new file mode 100644
index 0000000..9796b8c
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-mentor.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-soccer.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-soccer.png
new file mode 100644
index 0000000..9c0e809
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/koala-soccer.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/mountain.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/mountain.png
new file mode 100644
index 0000000..115fb9c
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/kody-notes/mountain.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/0.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/0.png
new file mode 100644
index 0000000..b04ba30
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/0.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/1.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/1.png
new file mode 100644
index 0000000..307f0a8
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/1.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/2.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/2.png
new file mode 100644
index 0000000..23b9822
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/2.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/3.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/3.png
new file mode 100644
index 0000000..ec4e949
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/3.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/4.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/4.png
new file mode 100644
index 0000000..138a0ad
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/4.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/5.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/5.png
new file mode 100644
index 0000000..bd2d0c1
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/5.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/6.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/6.png
new file mode 100644
index 0000000..4d34f94
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/6.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/7.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/7.png
new file mode 100644
index 0000000..2ccdd83
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/7.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/8.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/8.png
new file mode 100644
index 0000000..dfb7dab
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/8.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/9.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/9.png
new file mode 100644
index 0000000..1397419
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/notes/9.png differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/0.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/0.jpg
new file mode 100644
index 0000000..d3a9d7a
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/0.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/1.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/1.jpg
new file mode 100644
index 0000000..83c858c
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/1.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/2.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/2.jpg
new file mode 100644
index 0000000..de2564f
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/2.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/3.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/3.jpg
new file mode 100644
index 0000000..0e6a682
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/3.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/4.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/4.jpg
new file mode 100644
index 0000000..a9feae7
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/4.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/5.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/5.jpg
new file mode 100644
index 0000000..76c1183
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/5.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/6.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/6.jpg
new file mode 100644
index 0000000..a5b1e8e
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/6.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/7.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/7.jpg
new file mode 100644
index 0000000..70d3eeb
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/7.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/8.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/8.jpg
new file mode 100644
index 0000000..82df15f
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/8.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/9.jpg var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/9.jpg
new file mode 100644
index 0000000..0c61fbc
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/9.jpg differ
diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/kody.png var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/kody.png
new file mode 100644
index 0000000..a10de95
Binary files /dev/null and var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/39yuovg9r3j/tests/fixtures/images/user/kody.png differ
{
"type": "GitDiff",
"files": [
{
"type": "ChangedFile",
"chunks": [
{
"context": "export const meta: V2_MetaFunction = () => {",
"type": "Chunk",
"toFileRange": {
"start": 104,
"lines": 6
},
"fromFileRange": {
"start": 104,
"lines": 7
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 104,
"lineAfter": 104,
"content": "\t\t{ name: 'description', content: `Your own captain's log` },"
},
{
"type": "UnchangedLine",
"lineBefore": 105,
"lineAfter": 105,
"content": "\t]"
},
{
"type": "UnchangedLine",
"lineBefore": 106,
"lineAfter": 106,
"content": "}"
},
{
"type": "DeletedLine",
"lineBefore": 107,
"content": "// change"
},
{
"type": "UnchangedLine",
"lineBefore": 108,
"lineAfter": 107,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 109,
"lineAfter": 108,
"content": "export function ErrorBoundary() {"
},
{
"type": "UnchangedLine",
"lineBefore": 110,
"lineAfter": 109,
"content": "\treturn ("
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/root.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"type": "Chunk",
"toFileRange": {
"start": 1,
"lines": 20
},
"fromFileRange": {
"start": 1,
"lines": 28
},
"changes": [
{
"type": "DeletedLine",
"lineBefore": 1,
"content": "import { Response, type DataFunctionArgs } from '@remix-run/node'"
},
{
"type": "DeletedLine",
"lineBefore": 2,
"content": "import fs from 'node:fs'"
},
{
"type": "DeletedLine",
"lineBefore": 3,
"content": "import { PassThrough } from 'node:stream'"
},
{
"type": "DeletedLine",
"lineBefore": 4,
"content": "import { db } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 1,
"content": "import { type DataFunctionArgs } from '@remix-run/node'"
},
{
"type": "AddedLine",
"lineAfter": 2,
"content": "import { prisma } from '~/utils/db.server.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 5,
"lineAfter": 3,
"content": "import { invariantResponse } from '~/utils/misc.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 6,
"lineAfter": 4,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 7,
"lineAfter": 5,
"content": "export async function loader({ params }: DataFunctionArgs) {"
},
{
"type": "DeletedLine",
"lineBefore": 8,
"content": "\tinvariantResponse(params.imageId, 'Invalid image ID')"
},
{
"type": "DeletedLine",
"lineBefore": 9,
"content": "\tconst image = db.image.findFirst({"
},
{
"type": "DeletedLine",
"lineBefore": 10,
"content": "\t\twhere: { id: { equals: params.imageId } },"
},
{
"type": "AddedLine",
"lineAfter": 6,
"content": "\tinvariantResponse(params.imageId, 'Image ID is required', { status: 400 })"
},
{
"type": "AddedLine",
"lineAfter": 7,
"content": "\tconst image = await prisma.image.findUnique({"
},
{
"type": "AddedLine",
"lineAfter": 8,
"content": "\t\twhere: { id: params.imageId },"
},
{
"type": "AddedLine",
"lineAfter": 9,
"content": "\t\tselect: { contentType: true, file: { select: { blob: true } } },"
},
{
"type": "UnchangedLine",
"lineBefore": 11,
"lineAfter": 10,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 12,
"content": "\tinvariantResponse(image, 'Image not found', { status: 404 })"
},
{
"type": "UnchangedLine",
"lineBefore": 13,
"lineAfter": 11,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 14,
"content": "\tconst { filepath, contentType } = image"
},
{
"type": "DeletedLine",
"lineBefore": 15,
"content": "\tconst body = new PassThrough()"
},
{
"type": "DeletedLine",
"lineBefore": 16,
"content": "\tconst stream = fs.createReadStream(filepath)"
},
{
"type": "DeletedLine",
"lineBefore": 17,
"content": "\tstream.on('open', () => stream.pipe(body))"
},
{
"type": "DeletedLine",
"lineBefore": 18,
"content": "\tstream.on('error', err => body.end(err))"
},
{
"type": "DeletedLine",
"lineBefore": 19,
"content": "\tstream.on('end', () => body.end())"
},
{
"type": "DeletedLine",
"lineBefore": 20,
"content": "\treturn new Response(body, {"
},
{
"type": "DeletedLine",
"lineBefore": 21,
"content": "\t\tstatus: 200,"
},
{
"type": "AddedLine",
"lineAfter": 12,
"content": "\tinvariantResponse(image?.file, 'Not found', { status: 404 })"
},
{
"type": "AddedLine",
"lineAfter": 13,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 14,
"content": "\treturn new Response(image.file.blob, {"
},
{
"type": "UnchangedLine",
"lineBefore": 22,
"lineAfter": 15,
"content": "\t\theaders: {"
},
{
"type": "DeletedLine",
"lineBefore": 23,
"content": "\t\t\t'Content-Type': contentType,"
},
{
"type": "DeletedLine",
"lineBefore": 24,
"content": "\t\t\t'Content-Disposition': `inline; filename=\"${params.imageId}\"`,"
},
{
"type": "DeletedLine",
"lineBefore": 25,
"content": "\t\t\t'Cache-Control': 'public, max-age=31536000, immutable',"
},
{
"type": "AddedLine",
"lineAfter": 16,
"content": "\t\t\t'Content-Type': image.contentType,"
},
{
"type": "AddedLine",
"lineAfter": 17,
"content": "\t\t\t'Cache-Control': 'max-age=31536000',"
},
{
"type": "UnchangedLine",
"lineBefore": 26,
"lineAfter": 18,
"content": "\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 27,
"lineAfter": 19,
"content": "\t})"
},
{
"type": "UnchangedLine",
"lineBefore": 28,
"lineAfter": 20,
"content": "}"
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/routes/resources+/images.$imageId.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"type": "Chunk",
"toFileRange": {
"start": 1,
"lines": 69
},
"fromFileRange": {
"start": 1,
"lines": 32
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 1,
"lineAfter": 1,
"content": "import { json, type DataFunctionArgs } from '@remix-run/node'"
},
{
"type": "UnchangedLine",
"lineBefore": 2,
"lineAfter": 2,
"content": "import { Link, useLoaderData, type V2_MetaFunction } from '@remix-run/react'"
},
{
"type": "UnchangedLine",
"lineBefore": 3,
"lineAfter": 3,
"content": "import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'"
},
{
"type": "DeletedLine",
"lineBefore": 4,
"content": "import { db } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 4,
"content": "import { Spacer } from '~/components/spacer.tsx'"
},
{
"type": "AddedLine",
"lineAfter": 5,
"content": "import { Button } from '~/components/ui/button.tsx'"
},
{
"type": "AddedLine",
"lineAfter": 6,
"content": "import { prisma } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 7,
"content": "import { getUserImgSrc } from '~/utils/misc.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 5,
"lineAfter": 8,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 6,
"lineAfter": 9,
"content": "export async function loader({ params }: DataFunctionArgs) {"
},
{
"type": "DeletedLine",
"lineBefore": 7,
"content": "\tconst user = db.user.findFirst({"
},
{
"type": "AddedLine",
"lineAfter": 10,
"content": "\tconst user = await prisma.user.findFirst({"
},
{
"type": "AddedLine",
"lineAfter": 11,
"content": "\t\tselect: {"
},
{
"type": "AddedLine",
"lineAfter": 12,
"content": "\t\t\tname: true,"
},
{
"type": "AddedLine",
"lineAfter": 13,
"content": "\t\t\tusername: true,"
},
{
"type": "AddedLine",
"lineAfter": 14,
"content": "\t\t\tcreatedAt: true,"
},
{
"type": "AddedLine",
"lineAfter": 15,
"content": "\t\t\timage: { select: { id: true } },"
},
{
"type": "AddedLine",
"lineAfter": 16,
"content": "\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 8,
"lineAfter": 17,
"content": "\t\twhere: {"
},
{
"type": "DeletedLine",
"lineBefore": 9,
"content": "\t\t\tusername: {"
},
{
"type": "DeletedLine",
"lineBefore": 10,
"content": "\t\t\t\tequals: params.username,"
},
{
"type": "DeletedLine",
"lineBefore": 11,
"content": "\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 18,
"content": "\t\t\tusername: params.username,"
},
{
"type": "UnchangedLine",
"lineBefore": 12,
"lineAfter": 19,
"content": "\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 13,
"lineAfter": 20,
"content": "\t})"
},
{
"type": "UnchangedLine",
"lineBefore": 14,
"lineAfter": 21,
"content": "\tif (!user) {"
},
{
"type": "UnchangedLine",
"lineBefore": 15,
"lineAfter": 22,
"content": "\t\tthrow new Response('User not found', { status: 404 })"
},
{
"type": "UnchangedLine",
"lineBefore": 16,
"lineAfter": 23,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 17,
"content": "\treturn json({"
},
{
"type": "DeletedLine",
"lineBefore": 18,
"content": "\t\tuser: { name: user.name, username: user.username },"
},
{
"type": "DeletedLine",
"lineBefore": 19,
"content": "\t})"
},
{
"type": "AddedLine",
"lineAfter": 24,
"content": "\treturn json({ user, userJoinedDisplay: user.createdAt.toLocaleDateString() })"
},
{
"type": "UnchangedLine",
"lineBefore": 20,
"lineAfter": 25,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 21,
"lineAfter": 26,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 22,
"lineAfter": 27,
"content": "export default function ProfileRoute() {"
},
{
"type": "UnchangedLine",
"lineBefore": 23,
"lineAfter": 28,
"content": "\tconst data = useLoaderData<typeof loader>()"
},
{
"type": "AddedLine",
"lineAfter": 29,
"content": "\tconst user = data.user"
},
{
"type": "AddedLine",
"lineAfter": 30,
"content": "\tconst userDisplayName = user.name ?? user.username"
},
{
"type": "AddedLine",
"lineAfter": 31,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 24,
"lineAfter": 32,
"content": "\treturn ("
},
{
"type": "DeletedLine",
"lineBefore": 25,
"content": "\t\t<div className=\"container mb-48 mt-36\">"
},
{
"type": "DeletedLine",
"lineBefore": 26,
"content": "\t\t\t<h1 className=\"text-h1\">{data.user.name ?? data.user.username}</h1>"
},
{
"type": "DeletedLine",
"lineBefore": 27,
"content": "\t\t\t<Link to=\"notes\" className=\"underline\" prefetch=\"intent\">"
},
{
"type": "DeletedLine",
"lineBefore": 28,
"content": "\t\t\t\tNotes"
},
{
"type": "DeletedLine",
"lineBefore": 29,
"content": "\t\t\t</Link>"
},
{
"type": "AddedLine",
"lineAfter": 33,
"content": "\t\t<div className=\"container mb-48 mt-36 flex flex-col items-center justify-center\">"
},
{
"type": "AddedLine",
"lineAfter": 34,
"content": "\t\t\t<Spacer size=\"4xs\" />"
},
{
"type": "AddedLine",
"lineAfter": 35,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 36,
"content": "\t\t\t<div className=\"container flex flex-col items-center rounded-3xl bg-muted p-12\">"
},
{
"type": "AddedLine",
"lineAfter": 37,
"content": "\t\t\t\t<div className=\"relative w-52\">"
},
{
"type": "AddedLine",
"lineAfter": 38,
"content": "\t\t\t\t\t<div className=\"absolute -top-40\">"
},
{
"type": "AddedLine",
"lineAfter": 39,
"content": "\t\t\t\t\t\t<div className=\"relative\">"
},
{
"type": "AddedLine",
"lineAfter": 40,
"content": "\t\t\t\t\t\t\t<img"
},
{
"type": "AddedLine",
"lineAfter": 41,
"content": "\t\t\t\t\t\t\t\tsrc={getUserImgSrc(data.user.image?.id)}"
},
{
"type": "AddedLine",
"lineAfter": 42,
"content": "\t\t\t\t\t\t\t\talt={userDisplayName}"
},
{
"type": "AddedLine",
"lineAfter": 43,
"content": "\t\t\t\t\t\t\t\tclassName=\"h-52 w-52 rounded-full object-cover\""
},
{
"type": "AddedLine",
"lineAfter": 44,
"content": "\t\t\t\t\t\t\t/>"
},
{
"type": "AddedLine",
"lineAfter": 45,
"content": "\t\t\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 46,
"content": "\t\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 47,
"content": "\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 48,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 49,
"content": "\t\t\t\t<Spacer size=\"sm\" />"
},
{
"type": "AddedLine",
"lineAfter": 50,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 51,
"content": "\t\t\t\t<div className=\"flex flex-col items-center\">"
},
{
"type": "AddedLine",
"lineAfter": 52,
"content": "\t\t\t\t\t<div className=\"flex flex-wrap items-center justify-center gap-4\">"
},
{
"type": "AddedLine",
"lineAfter": 53,
"content": "\t\t\t\t\t\t<h1 className=\"text-center text-h2\">{userDisplayName}</h1>"
},
{
"type": "AddedLine",
"lineAfter": 54,
"content": "\t\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 55,
"content": "\t\t\t\t\t<p className=\"mt-2 text-center text-muted-foreground\">"
},
{
"type": "AddedLine",
"lineAfter": 56,
"content": "\t\t\t\t\t\tJoined {data.userJoinedDisplay}"
},
{
"type": "AddedLine",
"lineAfter": 57,
"content": "\t\t\t\t\t</p>"
},
{
"type": "AddedLine",
"lineAfter": 58,
"content": "\t\t\t\t\t<div className=\"mt-10 flex gap-4\">"
},
{
"type": "AddedLine",
"lineAfter": 59,
"content": "\t\t\t\t\t\t<Button asChild>"
},
{
"type": "AddedLine",
"lineAfter": 60,
"content": "\t\t\t\t\t\t\t<Link to=\"notes\" prefetch=\"intent\">"
},
{
"type": "AddedLine",
"lineAfter": 61,
"content": "\t\t\t\t\t\t\t\t{userDisplayName}'s notes"
},
{
"type": "AddedLine",
"lineAfter": 62,
"content": "\t\t\t\t\t\t\t</Link>"
},
{
"type": "AddedLine",
"lineAfter": 63,
"content": "\t\t\t\t\t\t</Button>"
},
{
"type": "AddedLine",
"lineAfter": 64,
"content": "\t\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 65,
"content": "\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 66,
"content": "\t\t\t</div>"
},
{
"type": "UnchangedLine",
"lineBefore": 30,
"lineAfter": 67,
"content": "\t\t</div>"
},
{
"type": "UnchangedLine",
"lineBefore": 31,
"lineAfter": 68,
"content": "\t)"
},
{
"type": "UnchangedLine",
"lineBefore": 32,
"lineAfter": 69,
"content": "}"
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/routes/users+/$username.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"context": "import {",
"type": "Chunk",
"toFileRange": {
"start": 7,
"lines": 26
},
"fromFileRange": {
"start": 7,
"lines": 29
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 7,
"lineAfter": 7,
"content": "} from '@remix-run/react'"
},
{
"type": "UnchangedLine",
"lineBefore": 8,
"lineAfter": 8,
"content": "import { floatingToolbarClassName } from '~/components/floating-toolbar.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 9,
"lineAfter": 9,
"content": "import { Button } from '~/components/ui/button.tsx'"
},
{
"type": "DeletedLine",
"lineBefore": 10,
"content": "import { db } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 10,
"content": "import { prisma } from '~/utils/db.server.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 11,
"lineAfter": 11,
"content": "import { invariantResponse } from '~/utils/misc.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 12,
"lineAfter": 12,
"content": "import { type loader as notesLoader } from './notes.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 13,
"lineAfter": 13,
"content": "import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 14,
"lineAfter": 14,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 15,
"lineAfter": 15,
"content": "export async function loader({ params }: DataFunctionArgs) {"
},
{
"type": "DeletedLine",
"lineBefore": 16,
"content": "\tconst note = db.note.findFirst({"
},
{
"type": "DeletedLine",
"lineBefore": 17,
"content": "\t\twhere: {"
},
{
"type": "DeletedLine",
"lineBefore": 18,
"content": "\t\t\tid: {"
},
{
"type": "DeletedLine",
"lineBefore": 19,
"content": "\t\t\t\tequals: params.noteId,"
},
{
"type": "AddedLine",
"lineAfter": 16,
"content": "\tconst note = await prisma.note.findFirst({"
},
{
"type": "AddedLine",
"lineAfter": 17,
"content": "\t\tselect: {"
},
{
"type": "AddedLine",
"lineAfter": 18,
"content": "\t\t\ttitle: true,"
},
{
"type": "AddedLine",
"lineAfter": 19,
"content": "\t\t\tcontent: true,"
},
{
"type": "AddedLine",
"lineAfter": 20,
"content": "\t\t\timages: {"
},
{
"type": "AddedLine",
"lineAfter": 21,
"content": "\t\t\t\tselect: { id: true, altText: true },"
},
{
"type": "UnchangedLine",
"lineBefore": 20,
"lineAfter": 22,
"content": "\t\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 21,
"lineAfter": 23,
"content": "\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 24,
"content": "\t\twhere: { id: params.noteId },"
},
{
"type": "UnchangedLine",
"lineBefore": 22,
"lineAfter": 25,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 23,
"content": "\tif (!note) {"
},
{
"type": "DeletedLine",
"lineBefore": 24,
"content": "\t\tthrow new Response('Note note found', { status: 404 })"
},
{
"type": "DeletedLine",
"lineBefore": 25,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 26,
"content": "\treturn json({"
},
{
"type": "DeletedLine",
"lineBefore": 27,
"content": "\t\tnote: {"
},
{
"type": "DeletedLine",
"lineBefore": 28,
"content": "\t\t\ttitle: note.title,"
},
{
"type": "DeletedLine",
"lineBefore": 29,
"content": "\t\t\tcontent: note.content,"
},
{
"type": "DeletedLine",
"lineBefore": 30,
"content": "\t\t\timages: note.images.map(i => ({ id: i.id, altText: i.altText })),"
},
{
"type": "DeletedLine",
"lineBefore": 31,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 32,
"content": "\t})"
},
{
"type": "AddedLine",
"lineAfter": 26,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 27,
"content": "\tinvariantResponse(note, 'Note not found', { status: 404 })"
},
{
"type": "AddedLine",
"lineAfter": 28,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 29,
"content": "\treturn json({ note })"
},
{
"type": "UnchangedLine",
"lineBefore": 33,
"lineAfter": 30,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 34,
"lineAfter": 31,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 35,
"lineAfter": 32,
"content": "export async function action({ request, params }: DataFunctionArgs) {"
}
]
},
{
"context": "export async function action({ request, params }: DataFunctionArgs) {",
"type": "Chunk",
"toFileRange": {
"start": 37,
"lines": 7
},
"fromFileRange": {
"start": 40,
"lines": 7
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 40,
"lineAfter": 37,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 41,
"lineAfter": 38,
"content": "\tinvariantResponse(intent === 'delete', 'Invalid intent')"
},
{
"type": "UnchangedLine",
"lineBefore": 42,
"lineAfter": 39,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 43,
"content": "\tdb.note.delete({ where: { id: { equals: params.noteId } } })"
},
{
"type": "AddedLine",
"lineAfter": 40,
"content": "\tawait prisma.note.delete({ where: { id: params.noteId } })"
},
{
"type": "UnchangedLine",
"lineBefore": 44,
"lineAfter": 41,
"content": "\treturn redirect(`/users/${params.username}/notes`)"
},
{
"type": "UnchangedLine",
"lineBefore": 45,
"lineAfter": 42,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 46,
"lineAfter": 43,
"content": ""
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/routes/users+/$username_+/notes.$noteId.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"context": "import {",
"type": "Chunk",
"toFileRange": {
"start": 15,
"lines": 35
},
"fromFileRange": {
"start": 15,
"lines": 36
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 15,
"lineAfter": 15,
"content": "\ttype DataFunctionArgs,"
},
{
"type": "UnchangedLine",
"lineBefore": 16,
"lineAfter": 16,
"content": "} from '@remix-run/node'"
},
{
"type": "UnchangedLine",
"lineBefore": 17,
"lineAfter": 17,
"content": "import { Form, useActionData, useLoaderData } from '@remix-run/react'"
},
{
"type": "AddedLine",
"lineAfter": 18,
"content": "import cuid from 'cuid'"
},
{
"type": "UnchangedLine",
"lineBefore": 18,
"lineAfter": 19,
"content": "import { useRef, useState } from 'react'"
},
{
"type": "UnchangedLine",
"lineBefore": 19,
"lineAfter": 20,
"content": "import { z } from 'zod'"
},
{
"type": "UnchangedLine",
"lineBefore": 20,
"lineAfter": 21,
"content": "import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 21,
"lineAfter": 22,
"content": "import { floatingToolbarClassName } from '~/components/floating-toolbar.tsx'"
},
{
"type": "AddedLine",
"lineAfter": 23,
"content": "import { ErrorList, Field, TextareaField } from '~/components/forms.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 22,
"lineAfter": 24,
"content": "import { Button } from '~/components/ui/button.tsx'"
},
{
"type": "DeletedLine",
"lineBefore": 23,
"content": "import { Input } from '~/components/ui/input.tsx'"
},
{
"type": "AddedLine",
"lineAfter": 25,
"content": "import { Icon } from '~/components/ui/icon.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 24,
"lineAfter": 26,
"content": "import { Label } from '~/components/ui/label.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 25,
"lineAfter": 27,
"content": "import { StatusButton } from '~/components/ui/status-button.tsx'"
},
{
"type": "UnchangedLine",
"lineBefore": 26,
"lineAfter": 28,
"content": "import { Textarea } from '~/components/ui/textarea.tsx'"
},
{
"type": "DeletedLine",
"lineBefore": 27,
"content": "import { db, updateNote } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 29,
"content": "import { prisma } from '~/utils/db.server.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 28,
"lineAfter": 30,
"content": "import { cn, invariantResponse, useIsSubmitting } from '~/utils/misc.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 29,
"lineAfter": 31,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 30,
"lineAfter": 32,
"content": "export async function loader({ params }: DataFunctionArgs) {"
},
{
"type": "DeletedLine",
"lineBefore": 31,
"content": "\tconst note = db.note.findFirst({"
},
{
"type": "DeletedLine",
"lineBefore": 32,
"content": "\t\twhere: {"
},
{
"type": "DeletedLine",
"lineBefore": 33,
"content": "\t\t\tid: {"
},
{
"type": "DeletedLine",
"lineBefore": 34,
"content": "\t\t\t\tequals: params.noteId,"
},
{
"type": "AddedLine",
"lineAfter": 33,
"content": "\tconst note = await prisma.note.findFirst({"
},
{
"type": "AddedLine",
"lineAfter": 34,
"content": "\t\twhere: { id: params.noteId },"
},
{
"type": "AddedLine",
"lineAfter": 35,
"content": "\t\tselect: {"
},
{
"type": "AddedLine",
"lineAfter": 36,
"content": "\t\t\ttitle: true,"
},
{
"type": "AddedLine",
"lineAfter": 37,
"content": "\t\t\tcontent: true,"
},
{
"type": "AddedLine",
"lineAfter": 38,
"content": "\t\t\timages: {"
},
{
"type": "AddedLine",
"lineAfter": 39,
"content": "\t\t\t\tselect: { id: true, altText: true },"
},
{
"type": "UnchangedLine",
"lineBefore": 35,
"lineAfter": 40,
"content": "\t\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 36,
"lineAfter": 41,
"content": "\t\t},"
},
{
"type": "UnchangedLine",
"lineBefore": 37,
"lineAfter": 42,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 38,
"content": "\tif (!note) {"
},
{
"type": "DeletedLine",
"lineBefore": 39,
"content": "\t\tthrow new Response('Note note found', { status: 404 })"
},
{
"type": "DeletedLine",
"lineBefore": 40,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 41,
"content": "\treturn json({"
},
{
"type": "DeletedLine",
"lineBefore": 42,
"content": "\t\tnote: {"
},
{
"type": "DeletedLine",
"lineBefore": 43,
"content": "\t\t\ttitle: note.title,"
},
{
"type": "DeletedLine",
"lineBefore": 44,
"content": "\t\t\tcontent: note.content,"
},
{
"type": "DeletedLine",
"lineBefore": 45,
"content": "\t\t\timages: note.images.map(i => ({ id: i.id, altText: i.altText })),"
},
{
"type": "DeletedLine",
"lineBefore": 46,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 47,
"content": "\t})"
},
{
"type": "AddedLine",
"lineAfter": 43,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 44,
"content": "\tinvariantResponse(note, 'Note not found', { status: 404 })"
},
{
"type": "AddedLine",
"lineAfter": 45,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 46,
"content": "\treturn json({ note })"
},
{
"type": "UnchangedLine",
"lineBefore": 48,
"lineAfter": 47,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 49,
"lineAfter": 48,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 50,
"lineAfter": 49,
"content": "const titleMinLength = 1"
}
]
},
{
"context": "export async function action({ request, params }: DataFunctionArgs) {",
"type": "Chunk",
"toFileRange": {
"start": 87,
"lines": 67
},
"fromFileRange": {
"start": 88,
"lines": 31
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 88,
"lineAfter": 87,
"content": "\tif (!submission.value) {"
},
{
"type": "UnchangedLine",
"lineBefore": 89,
"lineAfter": 88,
"content": "\t\treturn json({ status: 'error', submission } as const, { status: 400 })"
},
{
"type": "UnchangedLine",
"lineBefore": 90,
"lineAfter": 89,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 91,
"content": "\tconst { title, content, images } = submission.value"
},
{
"type": "DeletedLine",
"lineBefore": 92,
"content": "\tawait updateNote({ id: params.noteId, title, content, images })"
},
{
"type": "UnchangedLine",
"lineBefore": 93,
"lineAfter": 90,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 94,
"content": "\treturn redirect(`/users/${params.username}/notes/${params.noteId}`)"
},
{
"type": "DeletedLine",
"lineBefore": 95,
"content": "}"
},
{
"type": "AddedLine",
"lineAfter": 91,
"content": "\tconst { title, content, images = [] } = submission.value"
},
{
"type": "UnchangedLine",
"lineBefore": 96,
"lineAfter": 92,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 97,
"content": "function ErrorList({"
},
{
"type": "DeletedLine",
"lineBefore": 98,
"content": "\tid,"
},
{
"type": "DeletedLine",
"lineBefore": 99,
"content": "\terrors,"
},
{
"type": "DeletedLine",
"lineBefore": 100,
"content": "}: {"
},
{
"type": "DeletedLine",
"lineBefore": 101,
"content": "\tid?: string"
},
{
"type": "DeletedLine",
"lineBefore": 102,
"content": "\terrors?: Array<string> | string | null"
},
{
"type": "DeletedLine",
"lineBefore": 103,
"content": "}) {"
},
{
"type": "DeletedLine",
"lineBefore": 104,
"content": "\tif (!errors) return null"
},
{
"type": "DeletedLine",
"lineBefore": 105,
"content": "\terrors = Array.isArray(errors) ? errors : [errors]"
},
{
"type": "AddedLine",
"lineAfter": 93,
"content": "\tawait prisma.$transaction(async p => {"
},
{
"type": "AddedLine",
"lineAfter": 94,
"content": "\t\tconst updatedImages = await Promise.all("
},
{
"type": "AddedLine",
"lineAfter": 95,
"content": "\t\t\timages.map(async image => {"
},
{
"type": "AddedLine",
"lineAfter": 96,
"content": "\t\t\t\tif (image.file.size > 0) {"
},
{
"type": "AddedLine",
"lineAfter": 97,
"content": "\t\t\t\t\tconsole.log('file has been uploaded')"
},
{
"type": "AddedLine",
"lineAfter": 98,
"content": "\t\t\t\t\tconst blob = Buffer.from(await image.file.arrayBuffer())"
},
{
"type": "AddedLine",
"lineAfter": 99,
"content": "\t\t\t\t\tif (image.id) {"
},
{
"type": "AddedLine",
"lineAfter": 100,
"content": "\t\t\t\t\t\tconst newId = cuid()"
},
{
"type": "AddedLine",
"lineAfter": 101,
"content": "\t\t\t\t\t\tconsole.log('replacing existing image', image.id, newId)"
},
{
"type": "AddedLine",
"lineAfter": 102,
"content": "\t\t\t\t\t\tconst replaced = await p.image.update({"
},
{
"type": "AddedLine",
"lineAfter": 103,
"content": "\t\t\t\t\t\t\tselect: { id: true },"
},
{
"type": "AddedLine",
"lineAfter": 104,
"content": "\t\t\t\t\t\t\twhere: { id: image.id },"
},
{
"type": "AddedLine",
"lineAfter": 105,
"content": "\t\t\t\t\t\t\tdata: {"
},
{
"type": "AddedLine",
"lineAfter": 106,
"content": "\t\t\t\t\t\t\t\tid: newId,"
},
{
"type": "AddedLine",
"lineAfter": 107,
"content": "\t\t\t\t\t\t\t\taltText: image.altText,"
},
{
"type": "AddedLine",
"lineAfter": 108,
"content": "\t\t\t\t\t\t\t\tfile: { update: { blob: blob } },"
},
{
"type": "AddedLine",
"lineAfter": 109,
"content": "\t\t\t\t\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 110,
"content": "\t\t\t\t\t\t})"
},
{
"type": "AddedLine",
"lineAfter": 111,
"content": "\t\t\t\t\t\tconsole.log({ replaced })"
},
{
"type": "AddedLine",
"lineAfter": 112,
"content": "\t\t\t\t\t\treturn { id: newId }"
},
{
"type": "AddedLine",
"lineAfter": 113,
"content": "\t\t\t\t\t} else {"
},
{
"type": "AddedLine",
"lineAfter": 114,
"content": "\t\t\t\t\t\tconsole.log('creating new image')"
},
{
"type": "AddedLine",
"lineAfter": 115,
"content": "\t\t\t\t\t\tconst i = await p.image.create({"
},
{
"type": "AddedLine",
"lineAfter": 116,
"content": "\t\t\t\t\t\t\tselect: { id: true },"
},
{
"type": "AddedLine",
"lineAfter": 117,
"content": "\t\t\t\t\t\t\tdata: {"
},
{
"type": "AddedLine",
"lineAfter": 118,
"content": "\t\t\t\t\t\t\t\taltText: image.altText,"
},
{
"type": "AddedLine",
"lineAfter": 119,
"content": "\t\t\t\t\t\t\t\tcontentType: image.file.type,"
},
{
"type": "AddedLine",
"lineAfter": 120,
"content": "\t\t\t\t\t\t\t\tfile: { create: { blob } },"
},
{
"type": "AddedLine",
"lineAfter": 121,
"content": "\t\t\t\t\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 122,
"content": "\t\t\t\t\t\t})"
},
{
"type": "AddedLine",
"lineAfter": 123,
"content": "\t\t\t\t\t\treturn i"
},
{
"type": "AddedLine",
"lineAfter": 124,
"content": "\t\t\t\t\t}"
},
{
"type": "AddedLine",
"lineAfter": 125,
"content": "\t\t\t\t} else if (image.id) {"
},
{
"type": "AddedLine",
"lineAfter": 126,
"content": "\t\t\t\t\tconsole.log('updating existing image')"
},
{
"type": "AddedLine",
"lineAfter": 127,
"content": "\t\t\t\t\treturn await p.image.update({"
},
{
"type": "AddedLine",
"lineAfter": 128,
"content": "\t\t\t\t\t\tselect: { id: true },"
},
{
"type": "AddedLine",
"lineAfter": 129,
"content": "\t\t\t\t\t\twhere: { id: image.id },"
},
{
"type": "AddedLine",
"lineAfter": 130,
"content": "\t\t\t\t\t\tdata: { altText: image.altText },"
},
{
"type": "AddedLine",
"lineAfter": 131,
"content": "\t\t\t\t\t})"
},
{
"type": "AddedLine",
"lineAfter": 132,
"content": "\t\t\t\t}"
},
{
"type": "AddedLine",
"lineAfter": 133,
"content": "\t\t\t}),"
},
{
"type": "AddedLine",
"lineAfter": 134,
"content": "\t\t)"
},
{
"type": "AddedLine",
"lineAfter": 135,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 136,
"content": "\t\tconsole.log('updatedImages', updatedImages)"
},
{
"type": "AddedLine",
"lineAfter": 137,
"content": ""
},
{
"type": "AddedLine",
"lineAfter": 138,
"content": "\t\tawait p.note.update({"
},
{
"type": "AddedLine",
"lineAfter": 139,
"content": "\t\t\twhere: { id: params.noteId },"
},
{
"type": "AddedLine",
"lineAfter": 140,
"content": "\t\t\tdata: {"
},
{
"type": "AddedLine",
"lineAfter": 141,
"content": "\t\t\t\ttitle,"
},
{
"type": "AddedLine",
"lineAfter": 142,
"content": "\t\t\t\tcontent,"
},
{
"type": "AddedLine",
"lineAfter": 143,
"content": "\t\t\t\timages: {"
},
{
"type": "AddedLine",
"lineAfter": 144,
"content": "\t\t\t\t\tset: updatedImages.filter(Boolean),"
},
{
"type": "AddedLine",
"lineAfter": 145,
"content": "\t\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 146,
"content": "\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 147,
"content": "\t\t})"
},
{
"type": "AddedLine",
"lineAfter": 148,
"content": "\t})"
},
{
"type": "UnchangedLine",
"lineBefore": 106,
"lineAfter": 149,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 107,
"content": "\treturn errors.length ? ("
},
{
"type": "DeletedLine",
"lineBefore": 108,
"content": "\t\t<ul id={id} className=\"flex flex-col gap-1\">"
},
{
"type": "DeletedLine",
"lineBefore": 109,
"content": "\t\t\t{errors.map((error, i) => ("
},
{
"type": "DeletedLine",
"lineBefore": 110,
"content": "\t\t\t\t<li key={i} className=\"text-[10px] text-foreground-danger\">"
},
{
"type": "DeletedLine",
"lineBefore": 111,
"content": "\t\t\t\t\t{error}"
},
{
"type": "DeletedLine",
"lineBefore": 112,
"content": "\t\t\t\t</li>"
},
{
"type": "DeletedLine",
"lineBefore": 113,
"content": "\t\t\t))}"
},
{
"type": "DeletedLine",
"lineBefore": 114,
"content": "\t\t</ul>"
},
{
"type": "DeletedLine",
"lineBefore": 115,
"content": "\t) : null"
},
{
"type": "AddedLine",
"lineAfter": 150,
"content": "\treturn redirect(`/users/${params.username}/notes/${params.noteId}`)"
},
{
"type": "UnchangedLine",
"lineBefore": 116,
"lineAfter": 151,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 117,
"lineAfter": 152,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 118,
"lineAfter": 153,
"content": "export default function NoteEdit() {"
}
]
},
{
"context": "export default function NoteEdit() {",
"type": "Chunk",
"toFileRange": {
"start": 185,
"lines": 21
},
"fromFileRange": {
"start": 150,
"lines": 31
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 150,
"lineAfter": 185,
"content": "\t\t\t\t*/}"
},
{
"type": "UnchangedLine",
"lineBefore": 151,
"lineAfter": 186,
"content": "\t\t\t\t<button type=\"submit\" className=\"hidden\" />"
},
{
"type": "UnchangedLine",
"lineBefore": 152,
"lineAfter": 187,
"content": "\t\t\t\t<div className=\"flex flex-col gap-1\">"
},
{
"type": "DeletedLine",
"lineBefore": 153,
"content": "\t\t\t\t\t<div>"
},
{
"type": "DeletedLine",
"lineBefore": 154,
"content": "\t\t\t\t\t\t<Label htmlFor={fields.title.id}>Title</Label>"
},
{
"type": "DeletedLine",
"lineBefore": 155,
"content": "\t\t\t\t\t\t<Input"
},
{
"type": "DeletedLine",
"lineBefore": 156,
"content": "\t\t\t\t\t\t\tautoFocus"
},
{
"type": "DeletedLine",
"lineBefore": 157,
"content": "\t\t\t\t\t\t\t{...conform.input(fields.title, { ariaAttributes: true })}"
},
{
"type": "DeletedLine",
"lineBefore": 158,
"content": "\t\t\t\t\t\t/>"
},
{
"type": "DeletedLine",
"lineBefore": 159,
"content": "\t\t\t\t\t\t<div className=\"min-h-[32px] px-4 pb-3 pt-1\">"
},
{
"type": "DeletedLine",
"lineBefore": 160,
"content": "\t\t\t\t\t\t\t<ErrorList"
},
{
"type": "DeletedLine",
"lineBefore": 161,
"content": "\t\t\t\t\t\t\t\tid={fields.title.errorId}"
},
{
"type": "DeletedLine",
"lineBefore": 162,
"content": "\t\t\t\t\t\t\t\terrors={fields.title.errors}"
},
{
"type": "DeletedLine",
"lineBefore": 163,
"content": "\t\t\t\t\t\t\t/>"
},
{
"type": "DeletedLine",
"lineBefore": 164,
"content": "\t\t\t\t\t\t</div>"
},
{
"type": "DeletedLine",
"lineBefore": 165,
"content": "\t\t\t\t\t</div>"
},
{
"type": "DeletedLine",
"lineBefore": 166,
"content": "\t\t\t\t\t<div>"
},
{
"type": "DeletedLine",
"lineBefore": 167,
"content": "\t\t\t\t\t\t<Label htmlFor={fields.content.id}>Content</Label>"
},
{
"type": "DeletedLine",
"lineBefore": 168,
"content": "\t\t\t\t\t\t<Textarea"
},
{
"type": "DeletedLine",
"lineBefore": 169,
"content": "\t\t\t\t\t\t\t{...conform.textarea(fields.content, { ariaAttributes: true })}"
},
{
"type": "DeletedLine",
"lineBefore": 170,
"content": "\t\t\t\t\t\t/>"
},
{
"type": "DeletedLine",
"lineBefore": 171,
"content": "\t\t\t\t\t\t<div className=\"min-h-[32px] px-4 pb-3 pt-1\">"
},
{
"type": "DeletedLine",
"lineBefore": 172,
"content": "\t\t\t\t\t\t\t<ErrorList"
},
{
"type": "DeletedLine",
"lineBefore": 173,
"content": "\t\t\t\t\t\t\t\tid={fields.content.errorId}"
},
{
"type": "DeletedLine",
"lineBefore": 174,
"content": "\t\t\t\t\t\t\t\terrors={fields.content.errors}"
},
{
"type": "DeletedLine",
"lineBefore": 175,
"content": "\t\t\t\t\t\t\t/>"
},
{
"type": "DeletedLine",
"lineBefore": 176,
"content": "\t\t\t\t\t\t</div>"
},
{
"type": "DeletedLine",
"lineBefore": 177,
"content": "\t\t\t\t\t</div>"
},
{
"type": "AddedLine",
"lineAfter": 188,
"content": "\t\t\t\t\t<Field"
},
{
"type": "AddedLine",
"lineAfter": 189,
"content": "\t\t\t\t\t\tlabelProps={{ children: 'Title' }}"
},
{
"type": "AddedLine",
"lineAfter": 190,
"content": "\t\t\t\t\t\tinputProps={{"
},
{
"type": "AddedLine",
"lineAfter": 191,
"content": "\t\t\t\t\t\t\tautoFocus: true,"
},
{
"type": "AddedLine",
"lineAfter": 192,
"content": "\t\t\t\t\t\t\t...conform.input(fields.title, { ariaAttributes: true }),"
},
{
"type": "AddedLine",
"lineAfter": 193,
"content": "\t\t\t\t\t\t}}"
},
{
"type": "AddedLine",
"lineAfter": 194,
"content": "\t\t\t\t\t\terrors={fields.title.errors}"
},
{
"type": "AddedLine",
"lineAfter": 195,
"content": "\t\t\t\t\t/>"
},
{
"type": "AddedLine",
"lineAfter": 196,
"content": "\t\t\t\t\t<TextareaField"
},
{
"type": "AddedLine",
"lineAfter": 197,
"content": "\t\t\t\t\t\tlabelProps={{ children: 'Content' }}"
},
{
"type": "AddedLine",
"lineAfter": 198,
"content": "\t\t\t\t\t\ttextareaProps={{"
},
{
"type": "AddedLine",
"lineAfter": 199,
"content": "\t\t\t\t\t\t\t...conform.textarea(fields.content, { ariaAttributes: true }),"
},
{
"type": "AddedLine",
"lineAfter": 200,
"content": "\t\t\t\t\t\t}}"
},
{
"type": "AddedLine",
"lineAfter": 201,
"content": "\t\t\t\t\t\terrors={fields.content.errors}"
},
{
"type": "AddedLine",
"lineAfter": 202,
"content": "\t\t\t\t\t/>"
},
{
"type": "UnchangedLine",
"lineBefore": 178,
"lineAfter": 203,
"content": "\t\t\t\t\t<div>"
},
{
"type": "UnchangedLine",
"lineBefore": 179,
"lineAfter": 204,
"content": "\t\t\t\t\t\t<Label>Images</Label>"
},
{
"type": "UnchangedLine",
"lineBefore": 180,
"lineAfter": 205,
"content": "\t\t\t\t\t\t<ul className=\"flex flex-col gap-4\">"
}
]
},
{
"context": "export default function NoteEdit() {",
"type": "Chunk",
"toFileRange": {
"start": 212,
"lines": 9
},
"fromFileRange": {
"start": 187,
"lines": 7
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 187,
"lineAfter": 212,
"content": "\t\t\t\t\t\t\t\t\t\tclassName=\"absolute right-0 top-0 text-destructive\""
},
{
"type": "UnchangedLine",
"lineBefore": 188,
"lineAfter": 213,
"content": "\t\t\t\t\t\t\t\t\t\t{...list.remove(fields.images.name, { index })}"
},
{
"type": "UnchangedLine",
"lineBefore": 189,
"lineAfter": 214,
"content": "\t\t\t\t\t\t\t\t\t>"
},
{
"type": "DeletedLine",
"lineBefore": 190,
"content": "\t\t\t\t\t\t\t\t\t\t<span aria-hidden>❌</span>{' '}"
},
{
"type": "AddedLine",
"lineAfter": 215,
"content": "\t\t\t\t\t\t\t\t\t\t<span aria-hidden>"
},
{
"type": "AddedLine",
"lineAfter": 216,
"content": "\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"cross-1\" />"
},
{
"type": "AddedLine",
"lineAfter": 217,
"content": "\t\t\t\t\t\t\t\t\t\t</span>{' '}"
},
{
"type": "UnchangedLine",
"lineBefore": 191,
"lineAfter": 218,
"content": "\t\t\t\t\t\t\t\t\t\t<span className=\"sr-only\">Remove image {index + 1}</span>"
},
{
"type": "UnchangedLine",
"lineBefore": 192,
"lineAfter": 219,
"content": "\t\t\t\t\t\t\t\t\t</button>"
},
{
"type": "UnchangedLine",
"lineBefore": 193,
"lineAfter": 220,
"content": "\t\t\t\t\t\t\t\t\t<ImageChooser config={image} />"
}
]
},
{
"context": "export default function NoteEdit() {",
"type": "Chunk",
"toFileRange": {
"start": 226,
"lines": 9
},
"fromFileRange": {
"start": 199,
"lines": 7
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 199,
"lineAfter": 226,
"content": "\t\t\t\t\t\tclassName=\"mt-3\""
},
{
"type": "UnchangedLine",
"lineBefore": 200,
"lineAfter": 227,
"content": "\t\t\t\t\t\t{...list.append(fields.images.name, { defaultValue: {} })}"
},
{
"type": "UnchangedLine",
"lineBefore": 201,
"lineAfter": 228,
"content": "\t\t\t\t\t>"
},
{
"type": "DeletedLine",
"lineBefore": 202,
"content": "\t\t\t\t\t\t<span aria-hidden>➕ Image</span>{' '}"
},
{
"type": "AddedLine",
"lineAfter": 229,
"content": "\t\t\t\t\t\t<span aria-hidden>"
},
{
"type": "AddedLine",
"lineAfter": 230,
"content": "\t\t\t\t\t\t\t<Icon name=\"plus\">Image</Icon>"
},
{
"type": "AddedLine",
"lineAfter": 231,
"content": "\t\t\t\t\t\t</span>{' '}"
},
{
"type": "UnchangedLine",
"lineBefore": 203,
"lineAfter": 232,
"content": "\t\t\t\t\t\t<span className=\"sr-only\">Add image</span>"
},
{
"type": "UnchangedLine",
"lineBefore": 204,
"lineAfter": 233,
"content": "\t\t\t\t\t</Button>"
},
{
"type": "UnchangedLine",
"lineBefore": 205,
"lineAfter": 234,
"content": "\t\t\t\t</div>"
}
]
},
{
"context": "function ImageChooser({",
"type": "Chunk",
"toFileRange": {
"start": 296,
"lines": 7
},
"fromFileRange": {
"start": 267,
"lines": 7
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 267,
"lineAfter": 296,
"content": "\t\t\t\t\t\t\t\t</div>"
},
{
"type": "UnchangedLine",
"lineBefore": 268,
"lineAfter": 297,
"content": "\t\t\t\t\t\t\t) : ("
},
{
"type": "UnchangedLine",
"lineBefore": 269,
"lineAfter": 298,
"content": "\t\t\t\t\t\t\t\t<div className=\"flex h-32 w-32 items-center justify-center rounded-lg border border-muted-foreground text-4xl text-muted-foreground\">"
},
{
"type": "DeletedLine",
"lineBefore": 270,
"content": "\t\t\t\t\t\t\t\t\t➕"
},
{
"type": "AddedLine",
"lineAfter": 299,
"content": "\t\t\t\t\t\t\t\t\t<Icon name=\"plus\" />"
},
{
"type": "UnchangedLine",
"lineBefore": 271,
"lineAfter": 300,
"content": "\t\t\t\t\t\t\t\t</div>"
},
{
"type": "UnchangedLine",
"lineBefore": 272,
"lineAfter": 301,
"content": "\t\t\t\t\t\t\t)}"
},
{
"type": "UnchangedLine",
"lineBefore": 273,
"lineAfter": 302,
"content": "\t\t\t\t\t\t\t{existingImage ? ("
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/routes/users+/$username_+/notes.$noteId_.edit.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"type": "Chunk",
"toFileRange": {
"start": 1,
"lines": 25
},
"fromFileRange": {
"start": 1,
"lines": 31
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 1,
"lineAfter": 1,
"content": "import { json, type DataFunctionArgs } from '@remix-run/node'"
},
{
"type": "UnchangedLine",
"lineBefore": 2,
"lineAfter": 2,
"content": "import { Link, NavLink, Outlet, useLoaderData } from '@remix-run/react'"
},
{
"type": "UnchangedLine",
"lineBefore": 3,
"lineAfter": 3,
"content": "import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'"
},
{
"type": "DeletedLine",
"lineBefore": 4,
"content": "import { db } from '~/utils/db.server.ts'"
},
{
"type": "DeletedLine",
"lineBefore": 5,
"content": "import { cn } from '~/utils/misc.ts'"
},
{
"type": "AddedLine",
"lineAfter": 4,
"content": "import { prisma } from '~/utils/db.server.ts'"
},
{
"type": "AddedLine",
"lineAfter": 5,
"content": "import { cn, getUserImgSrc } from '~/utils/misc.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 6,
"lineAfter": 6,
"content": ""
},
{
"type": "UnchangedLine",
"lineBefore": 7,
"lineAfter": 7,
"content": "export async function loader({ params }: DataFunctionArgs) {"
},
{
"type": "DeletedLine",
"lineBefore": 8,
"content": "\tconst owner = db.user.findFirst({"
},
{
"type": "DeletedLine",
"lineBefore": 9,
"content": "\t\twhere: {"
},
{
"type": "DeletedLine",
"lineBefore": 10,
"content": "\t\t\tusername: {"
},
{
"type": "DeletedLine",
"lineBefore": 11,
"content": "\t\t\t\tequals: params.username,"
},
{
"type": "DeletedLine",
"lineBefore": 12,
"content": "\t\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 13,
"content": "\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 8,
"content": "\tconst owner = await prisma.user.findFirst({"
},
{
"type": "AddedLine",
"lineAfter": 9,
"content": "\t\tselect: { name: true, username: true, image: { select: { id: true } } },"
},
{
"type": "AddedLine",
"lineAfter": 10,
"content": "\t\twhere: { username: params.username },"
},
{
"type": "UnchangedLine",
"lineBefore": 14,
"lineAfter": 11,
"content": "\t})"
},
{
"type": "UnchangedLine",
"lineBefore": 15,
"lineAfter": 12,
"content": "\tif (!owner) {"
},
{
"type": "UnchangedLine",
"lineBefore": 16,
"lineAfter": 13,
"content": "\t\tthrow new Response('Owner not found', { status: 404 })"
},
{
"type": "UnchangedLine",
"lineBefore": 17,
"lineAfter": 14,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 18,
"content": "\tconst notes = db.note"
},
{
"type": "DeletedLine",
"lineBefore": 19,
"content": "\t\t.findMany({"
},
{
"type": "DeletedLine",
"lineBefore": 20,
"content": "\t\t\twhere: {"
},
{
"type": "DeletedLine",
"lineBefore": 21,
"content": "\t\t\t\towner: {"
},
{
"type": "DeletedLine",
"lineBefore": 22,
"content": "\t\t\t\t\tusername: {"
},
{
"type": "DeletedLine",
"lineBefore": 23,
"content": "\t\t\t\t\t\tequals: params.username,"
},
{
"type": "DeletedLine",
"lineBefore": 24,
"content": "\t\t\t\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 25,
"content": "\t\t\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 15,
"content": "\tconst notes = await prisma.note.findMany({"
},
{
"type": "AddedLine",
"lineAfter": 16,
"content": "\t\tselect: { id: true, title: true },"
},
{
"type": "AddedLine",
"lineAfter": 17,
"content": "\t\twhere: {"
},
{
"type": "AddedLine",
"lineAfter": 18,
"content": "\t\t\towner: {"
},
{
"type": "AddedLine",
"lineAfter": 19,
"content": "\t\t\t\tusername: params.username,"
},
{
"type": "UnchangedLine",
"lineBefore": 26,
"lineAfter": 20,
"content": "\t\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 27,
"content": "\t\t})"
},
{
"type": "DeletedLine",
"lineBefore": 28,
"content": "\t\t.map(({ id, title }) => ({ id, title }))"
},
{
"type": "AddedLine",
"lineAfter": 21,
"content": "\t\t},"
},
{
"type": "AddedLine",
"lineAfter": 22,
"content": "\t})"
},
{
"type": "UnchangedLine",
"lineBefore": 29,
"lineAfter": 23,
"content": "\treturn json({ owner, notes })"
},
{
"type": "UnchangedLine",
"lineBefore": 30,
"lineAfter": 24,
"content": "}"
},
{
"type": "UnchangedLine",
"lineBefore": 31,
"lineAfter": 25,
"content": ""
}
]
},
{
"context": "export default function NotesRoute() {",
"type": "Chunk",
"toFileRange": {
"start": 35,
"lines": 14
},
"fromFileRange": {
"start": 41,
"lines": 9
},
"changes": [
{
"type": "UnchangedLine",
"lineBefore": 41,
"lineAfter": 35,
"content": "\t\t\t\t\t<div className=\"absolute inset-0 flex flex-col\">"
},
{
"type": "UnchangedLine",
"lineBefore": 42,
"lineAfter": 36,
"content": "\t\t\t\t\t\t<Link"
},
{
"type": "UnchangedLine",
"lineBefore": 43,
"lineAfter": 37,
"content": "\t\t\t\t\t\t\tto={`/users/${data.owner.username}`}"
},
{
"type": "DeletedLine",
"lineBefore": 44,
"content": "\t\t\t\t\t\t\tclassName=\"pb-4 pl-8 pr-4 pt-12\""
},
{
"type": "AddedLine",
"lineAfter": 38,
"content": "\t\t\t\t\t\t\tclassName=\"flex flex-col items-center justify-center gap-2 bg-muted pb-4 pl-8 pr-4 pt-12 lg:flex-row lg:justify-start lg:gap-4\""
},
{
"type": "UnchangedLine",
"lineBefore": 45,
"lineAfter": 39,
"content": "\t\t\t\t\t\t>"
},
{
"type": "DeletedLine",
"lineBefore": 46,
"content": "\t\t\t\t\t\t\t<h1 className=\"text-base font-bold md:text-lg lg:text-left lg:text-2xl\">"
},
{
"type": "AddedLine",
"lineAfter": 40,
"content": "\t\t\t\t\t\t\t<img"
},
{
"type": "AddedLine",
"lineAfter": 41,
"content": "\t\t\t\t\t\t\t\tsrc={getUserImgSrc(data.owner.image?.id)}"
},
{
"type": "AddedLine",
"lineAfter": 42,
"content": "\t\t\t\t\t\t\t\talt={ownerDisplayName}"
},
{
"type": "AddedLine",
"lineAfter": 43,
"content": "\t\t\t\t\t\t\t\tclassName=\"h-16 w-16 rounded-full object-cover lg:h-24 lg:w-24\""
},
{
"type": "AddedLine",
"lineAfter": 44,
"content": "\t\t\t\t\t\t\t/>"
},
{
"type": "AddedLine",
"lineAfter": 45,
"content": "\t\t\t\t\t\t\t<h1 className=\"text-center text-base font-bold md:text-lg lg:text-left lg:text-2xl\">"
},
{
"type": "UnchangedLine",
"lineBefore": 47,
"lineAfter": 46,
"content": "\t\t\t\t\t\t\t\t{ownerDisplayName}'s Notes"
},
{
"type": "UnchangedLine",
"lineBefore": 48,
"lineAfter": 47,
"content": "\t\t\t\t\t\t\t</h1>"
},
{
"type": "UnchangedLine",
"lineBefore": 49,
"lineAfter": 48,
"content": "\t\t\t\t\t\t</Link>"
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/routes/users+/$username_+/notes.tsx"
},
{
"type": "ChangedFile",
"chunks": [
{
"type": "Chunk",
"toFileRange": {
"start": 1,
"lines": 10
},
"fromFileRange": {
"start": 1,
"lines": 231
},
"changes": [
{
"type": "DeletedLine",
"lineBefore": 1,
"content": "/**"
},
{
"type": "DeletedLine",
"lineBefore": 2,
"content": " * Don't worry too much about this file. It's just an in-memory \"database\""
},
{
"type": "DeletedLine",
"lineBefore": 3,
"content": " * for the purposes of our workshop. The data modeling workshop will cover"
},
{
"type": "DeletedLine",
"lineBefore": 4,
"content": " * the proper database."
},
{
"type": "DeletedLine",
"lineBefore": 5,
"content": " */"
},
{
"type": "DeletedLine",
"lineBefore": 6,
"content": "import { factory, manyOf, nullable, oneOf, primaryKey } from '@mswjs/data'"
},
{
"type": "DeletedLine",
"lineBefore": 7,
"content": "import path from 'node:path'"
},
{
"type": "DeletedLine",
"lineBefore": 8,
"content": "import fs from 'node:fs/promises'"
},
{
"type": "DeletedLine",
"lineBefore": 9,
"content": "import os from 'node:os'"
},
{
"type": "DeletedLine",
"lineBefore": 10,
"content": "import crypto from 'crypto'"
},
{
"type": "AddedLine",
"lineAfter": 1,
"content": "import { PrismaClient } from '@prisma/client'"
},
{
"type": "UnchangedLine",
"lineBefore": 11,
"lineAfter": 2,
"content": "import { singleton } from './singleton.server.ts'"
},
{
"type": "UnchangedLine",
"lineBefore": 12,
"lineAfter": 3,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 13,
"content": "const getId = () => crypto.randomBytes(16).toString('hex').slice(0, 8)"
},
{
"type": "DeletedLine",
"lineBefore": 14,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 15,
"content": "/*"
},
{
"type": "DeletedLine",
"lineBefore": 16,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 17,
"content": "model File {"
},
{
"type": "DeletedLine",
"lineBefore": 18,
"content": " id String @id @unique @default(cuid())"
},
{
"type": "DeletedLine",
"lineBefore": 19,
"content": " blob Bytes"
},
{
"type": "DeletedLine",
"lineBefore": 20,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 21,
"content": " createdAt DateTime @default(now())"
},
{
"type": "DeletedLine",
"lineBefore": 22,
"content": " updatedAt DateTime @updatedAt"
},
{
"type": "DeletedLine",
"lineBefore": 23,
"content": " image Image?"
},
{
"type": "DeletedLine",
"lineBefore": 24,
"content": "}"
},
{
"type": "DeletedLine",
"lineBefore": 25,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 26,
"content": "model Image {"
},
{
"type": "DeletedLine",
"lineBefore": 27,
"content": " file File @relation(fields: [fileId], references: [id], onDelete: Cascade, onUpdate: Cascade)"
},
{
"type": "DeletedLine",
"lineBefore": 28,
"content": " fileId String @unique"
},
{
"type": "DeletedLine",
"lineBefore": 29,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 30,
"content": " contentType String"
},
{
"type": "DeletedLine",
"lineBefore": 31,
"content": " altText String?"
},
{
"type": "DeletedLine",
"lineBefore": 32,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 33,
"content": " createdAt DateTime @default(now())"
},
{
"type": "DeletedLine",
"lineBefore": 34,
"content": " updatedAt DateTime @updatedAt"
},
{
"type": "DeletedLine",
"lineBefore": 35,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 36,
"content": " user User?"
},
{
"type": "DeletedLine",
"lineBefore": 37,
"content": "}"
},
{
"type": "DeletedLine",
"lineBefore": 38,
"content": "*/"
},
{
"type": "DeletedLine",
"lineBefore": 39,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 40,
"content": "export const db = singleton('db', () => {"
},
{
"type": "DeletedLine",
"lineBefore": 41,
"content": "\tconst db = factory({"
},
{
"type": "DeletedLine",
"lineBefore": 42,
"content": "\t\tuser: {"
},
{
"type": "DeletedLine",
"lineBefore": 43,
"content": "\t\t\tid: primaryKey(getId),"
},
{
"type": "DeletedLine",
"lineBefore": 44,
"content": "\t\t\temail: String,"
},
{
"type": "DeletedLine",
"lineBefore": 45,
"content": "\t\t\tusername: String,"
},
{
"type": "DeletedLine",
"lineBefore": 46,
"content": "\t\t\tname: nullable(String),"
},
{
"type": "DeletedLine",
"lineBefore": 47,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 48,
"content": "\t\t\tcreatedAt: () => new Date(),"
},
{
"type": "DeletedLine",
"lineBefore": 49,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 50,
"content": "\t\t\tnotes: manyOf('note'),"
},
{
"type": "DeletedLine",
"lineBefore": 51,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 52,
"content": "\t\tnote: {"
},
{
"type": "DeletedLine",
"lineBefore": 53,
"content": "\t\t\tid: primaryKey(getId),"
},
{
"type": "DeletedLine",
"lineBefore": 54,
"content": "\t\t\ttitle: String,"
},
{
"type": "DeletedLine",
"lineBefore": 55,
"content": "\t\t\tcontent: String,"
},
{
"type": "DeletedLine",
"lineBefore": 56,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 57,
"content": "\t\t\tcreatedAt: () => new Date(),"
},
{
"type": "DeletedLine",
"lineBefore": 58,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 59,
"content": "\t\t\towner: oneOf('user'),"
},
{
"type": "DeletedLine",
"lineBefore": 60,
"content": "\t\t\timages: manyOf('image'),"
},
{
"type": "DeletedLine",
"lineBefore": 61,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 62,
"content": "\t\timage: {"
},
{
"type": "DeletedLine",
"lineBefore": 63,
"content": "\t\t\tid: primaryKey(getId),"
},
{
"type": "DeletedLine",
"lineBefore": 64,
"content": "\t\t\tfilepath: String,"
},
{
"type": "DeletedLine",
"lineBefore": 65,
"content": "\t\t\tcontentType: String,"
},
{
"type": "DeletedLine",
"lineBefore": 66,
"content": "\t\t\taltText: nullable(String),"
},
{
"type": "DeletedLine",
"lineBefore": 67,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 68,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 69,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 70,
"content": "\tconst kody = db.user.create({"
},
{
"type": "DeletedLine",
"lineBefore": 71,
"content": "\t\tid: '9d6eba59daa2fc2078cf8205cd451041',"
},
{
"type": "DeletedLine",
"lineBefore": 72,
"content": "\t\temail: 'kody@kcd.dev',"
},
{
"type": "DeletedLine",
"lineBefore": 73,
"content": "\t\tusername: 'kody',"
},
{
"type": "DeletedLine",
"lineBefore": 74,
"content": "\t\tname: 'Kody',"
},
{
"type": "DeletedLine",
"lineBefore": 75,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 76,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 77,
"content": "\tconst kodyNotes = ["
},
{
"type": "DeletedLine",
"lineBefore": 78,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 79,
"content": "\t\t\tid: 'd27a197e',"
},
{
"type": "DeletedLine",
"lineBefore": 80,
"content": "\t\t\ttitle: 'Basic Koala Facts',"
},
{
"type": "DeletedLine",
"lineBefore": 81,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 82,
"content": "\t\t\t\t'Koalas are found in the eucalyptus forests of eastern Australia. They have grey fur with a cream-coloured chest, and strong, clawed feet, perfect for living in the branches of trees!',"
},
{
"type": "DeletedLine",
"lineBefore": 83,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 84,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 85,
"content": "\t\t\tid: '414f0c09',"
},
{
"type": "DeletedLine",
"lineBefore": 86,
"content": "\t\t\ttitle: 'Koalas like to cuddle',"
},
{
"type": "DeletedLine",
"lineBefore": 87,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 88,
"content": "\t\t\t\t'Cuddly critters, koalas measure about 60cm to 85cm long, and weigh about 14kg.',"
},
{
"type": "DeletedLine",
"lineBefore": 89,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 90,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 91,
"content": "\t\t\tid: '260366b1',"
},
{
"type": "DeletedLine",
"lineBefore": 92,
"content": "\t\t\ttitle: 'Not bears',"
},
{
"type": "DeletedLine",
"lineBefore": 93,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 94,
"content": "\t\t\t\t\"Although you may have heard people call them koala 'bears', these awesome animals aren’t bears at all – they are in fact marsupials. A group of mammals, most marsupials have pouches where their newborns develop.\","
},
{
"type": "DeletedLine",
"lineBefore": 95,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 96,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 97,
"content": "\t\t\tid: 'bb79cf45',"
},
{
"type": "DeletedLine",
"lineBefore": 98,
"content": "\t\t\ttitle: 'Snowboarding Adventure',"
},
{
"type": "DeletedLine",
"lineBefore": 99,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 100,
"content": "\t\t\t\t\"Today was an epic day on the slopes! Shredded fresh powder with my friends, caught some sick air, and even attempted a backflip. Can't wait for the next snowy adventure!\","
},
{
"type": "DeletedLine",
"lineBefore": 101,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 102,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 103,
"content": "\t\t\tid: '9f4308be',"
},
{
"type": "DeletedLine",
"lineBefore": 104,
"content": "\t\t\ttitle: 'Onewheel Tricks',"
},
{
"type": "DeletedLine",
"lineBefore": 105,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 106,
"content": "\t\t\t\t\"Mastered a new trick on my Onewheel today called '180 Spin'. It's exhilarating to carve through the streets while pulling off these rad moves. Time to level up and learn more!\","
},
{
"type": "DeletedLine",
"lineBefore": 107,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 108,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 109,
"content": "\t\t\tid: '306021fb',"
},
{
"type": "DeletedLine",
"lineBefore": 110,
"content": "\t\t\ttitle: 'Coding Dilemma',"
},
{
"type": "DeletedLine",
"lineBefore": 111,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 112,
"content": "\t\t\t\t\"Stuck on a bug in my latest coding project. Need to figure out why my function isn't returning the expected output. Time to dig deep, debug, and conquer this challenge!\","
},
{
"type": "DeletedLine",
"lineBefore": 113,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 114,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 115,
"content": "\t\t\tid: '16d4912a',"
},
{
"type": "DeletedLine",
"lineBefore": 116,
"content": "\t\t\ttitle: 'Coding Mentorship',"
},
{
"type": "DeletedLine",
"lineBefore": 117,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 118,
"content": "\t\t\t\t\"Had a fantastic coding mentoring session today with Sarah. Helped her understand the concept of recursion, and she made great progress. It's incredibly fulfilling to help others improve their coding skills.\","
},
{
"type": "DeletedLine",
"lineBefore": 119,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 120,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 121,
"content": "\t\t\tid: '3199199e',"
},
{
"type": "DeletedLine",
"lineBefore": 122,
"content": "\t\t\ttitle: 'Koala Fun Facts',"
},
{
"type": "DeletedLine",
"lineBefore": 123,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 124,
"content": "\t\t\t\t\"Did you know that koalas sleep for up to 20 hours a day? It's because their diet of eucalyptus leaves doesn't provide much energy. But when I'm awake, I enjoy munching on leaves, chilling in trees, and being the cuddliest koala around!\","
},
{
"type": "DeletedLine",
"lineBefore": 125,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 126,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 127,
"content": "\t\t\tid: '2030ffd3',"
},
{
"type": "DeletedLine",
"lineBefore": 128,
"content": "\t\t\ttitle: 'Skiing Adventure',"
},
{
"type": "DeletedLine",
"lineBefore": 129,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 130,
"content": "\t\t\t\t'Spent the day hitting the slopes on my skis. The fresh powder made for some incredible runs and breathtaking views. Skiing down the mountain at top speed is an adrenaline rush like no other!',"
},
{
"type": "DeletedLine",
"lineBefore": 131,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 132,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 133,
"content": "\t\t\tid: 'f375a804',"
},
{
"type": "DeletedLine",
"lineBefore": 134,
"content": "\t\t\ttitle: 'Code Jam Success',"
},
{
"type": "DeletedLine",
"lineBefore": 135,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 136,
"content": "\t\t\t\t'Participated in a coding competition today and secured the first place! The adrenaline, the challenging problems, and the satisfaction of finding optimal solutions—it was an amazing experience. Feeling proud and motivated to keep pushing my coding skills further!',"
},
{
"type": "DeletedLine",
"lineBefore": 137,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 138,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 139,
"content": "\t\t\tid: '562c541b',"
},
{
"type": "DeletedLine",
"lineBefore": 140,
"content": "\t\t\ttitle: 'Koala Conservation Efforts',"
},
{
"type": "DeletedLine",
"lineBefore": 141,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 142,
"content": "\t\t\t\t\"Joined a local conservation group to protect koalas and their habitats. Together, we're planting more eucalyptus trees, raising awareness about their endangered status, and working towards a sustainable future for these adorable creatures. Every small step counts!\","
},
{
"type": "DeletedLine",
"lineBefore": 143,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 144,
"content": "\t\t// extra long note to test scrolling"
},
{
"type": "DeletedLine",
"lineBefore": 145,
"content": "\t\t{"
},
{
"type": "DeletedLine",
"lineBefore": 146,
"content": "\t\t\tid: 'f67ca40b',"
},
{
"type": "DeletedLine",
"lineBefore": 147,
"content": "\t\t\ttitle: 'Game day',"
},
{
"type": "DeletedLine",
"lineBefore": 148,
"content": "\t\t\tcontent:"
},
{
"type": "DeletedLine",
"lineBefore": 149,
"content": "\t\t\t\t\"Just got back from the most amazing game. I've been playing soccer for a long time, but I've not once scored a goal. Well, today all that changed! I finally scored my first ever goal.\\n\\nI'm in an indoor league, and my team's not the best, but we're pretty good and I have fun, that's all that really matters. Anyway, I found myself at the other end of the field with the ball. It was just me and the goalie. I normally just kick the ball and hope it goes in, but the ball was already rolling toward the goal. The goalie was about to get the ball, so I had to charge. I managed to get possession of the ball just before the goalie got it. I brought it around the goalie and had a perfect shot. I screamed so loud in excitement. After all these years playing, I finally scored a goal!\\n\\nI know it's not a lot for most folks, but it meant a lot to me. We did end up winning the game by one. It makes me feel great that I had a part to play in that.\\n\\nIn this team, I'm the captain. I'm constantly cheering my team on. Even after getting injured, I continued to come and watch from the side-lines. I enjoy yelling (encouragingly) at my team mates and helping them be the best they can. I'm definitely not the best player by a long stretch. But I really enjoy the game. It's a great way to get exercise and have good social interactions once a week.\\n\\nThat said, it can be hard to keep people coming and paying dues and stuff. If people don't show up it can be really hard to find subs. I have a list of people I can text, but sometimes I can't find anyone.\\n\\nBut yeah, today was awesome. I felt like more than just a player that gets in the way of the opposition, but an actual asset to the team. Really great feeling.\\n\\nAnyway, I'm rambling at this point and really this is just so we can have a note that's pretty long to test things out. I think it's long enough now... Cheers!\","
},
{
"type": "DeletedLine",
"lineBefore": 150,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 151,
"content": "\t]"
},
{
"type": "DeletedLine",
"lineBefore": 152,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 153,
"content": "\tfor (const note of kodyNotes) {"
},
{
"type": "DeletedLine",
"lineBefore": 154,
"content": "\t\tdb.note.create({"
},
{
"type": "DeletedLine",
"lineBefore": 155,
"content": "\t\t\t...note,"
},
{
"type": "DeletedLine",
"lineBefore": 156,
"content": "\t\t\towner: kody,"
},
{
"type": "DeletedLine",
"lineBefore": 157,
"content": "\t\t})"
},
{
"type": "DeletedLine",
"lineBefore": 158,
"content": "\t}"
},
{
"type": "DeletedLine",
"lineBefore": 159,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 160,
"content": "\treturn db"
},
{
"type": "AddedLine",
"lineAfter": 4,
"content": "const prisma = singleton('prisma', () => {"
},
{
"type": "AddedLine",
"lineAfter": 5,
"content": "\tconst p = new PrismaClient()"
},
{
"type": "AddedLine",
"lineAfter": 6,
"content": "\tp.$connect()"
},
{
"type": "AddedLine",
"lineAfter": 7,
"content": "\treturn p"
},
{
"type": "UnchangedLine",
"lineBefore": 161,
"lineAfter": 8,
"content": "})"
},
{
"type": "UnchangedLine",
"lineBefore": 162,
"lineAfter": 9,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 163,
"content": "export async function updateNote({"
},
{
"type": "DeletedLine",
"lineBefore": 164,
"content": "\tid,"
},
{
"type": "DeletedLine",
"lineBefore": 165,
"content": "\ttitle,"
},
{
"type": "DeletedLine",
"lineBefore": 166,
"content": "\tcontent,"
},
{
"type": "DeletedLine",
"lineBefore": 167,
"content": "\timages,"
},
{
"type": "DeletedLine",
"lineBefore": 168,
"content": "}: {"
},
{
"type": "DeletedLine",
"lineBefore": 169,
"content": "\tid: string"
},
{
"type": "DeletedLine",
"lineBefore": 170,
"content": "\ttitle: string"
},
{
"type": "DeletedLine",
"lineBefore": 171,
"content": "\tcontent: string"
},
{
"type": "DeletedLine",
"lineBefore": 172,
"content": "\timages?: Array<{"
},
{
"type": "DeletedLine",
"lineBefore": 173,
"content": "\t\tid?: string"
},
{
"type": "DeletedLine",
"lineBefore": 174,
"content": "\t\tfile?: File"
},
{
"type": "DeletedLine",
"lineBefore": 175,
"content": "\t\taltText?: string"
},
{
"type": "DeletedLine",
"lineBefore": 176,
"content": "\t} | null>"
},
{
"type": "DeletedLine",
"lineBefore": 177,
"content": "}) {"
},
{
"type": "DeletedLine",
"lineBefore": 178,
"content": "\tconst noteImagePromises ="
},
{
"type": "DeletedLine",
"lineBefore": 179,
"content": "\t\timages?.map(async image => {"
},
{
"type": "DeletedLine",
"lineBefore": 180,
"content": "\t\t\tif (!image) return null"
},
{
"type": "DeletedLine",
"lineBefore": 181,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 182,
"content": "\t\t\tif (image.id) {"
},
{
"type": "DeletedLine",
"lineBefore": 183,
"content": "\t\t\t\tconst hasReplacement = (image?.file?.size || 0) > 0"
},
{
"type": "DeletedLine",
"lineBefore": 184,
"content": "\t\t\t\tconst filepath ="
},
{
"type": "DeletedLine",
"lineBefore": 185,
"content": "\t\t\t\t\timage.file && hasReplacement"
},
{
"type": "DeletedLine",
"lineBefore": 186,
"content": "\t\t\t\t\t\t? await writeImage(image.file)"
},
{
"type": "DeletedLine",
"lineBefore": 187,
"content": "\t\t\t\t\t\t: undefined"
},
{
"type": "DeletedLine",
"lineBefore": 188,
"content": "\t\t\t\t// update the ID so caching is invalidated"
},
{
"type": "DeletedLine",
"lineBefore": 189,
"content": "\t\t\t\tconst id = image.file && hasReplacement ? getId() : image.id"
},
{
"type": "DeletedLine",
"lineBefore": 190,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 191,
"content": "\t\t\t\treturn db.image.update({"
},
{
"type": "DeletedLine",
"lineBefore": 192,
"content": "\t\t\t\t\twhere: { id: { equals: image.id } },"
},
{
"type": "DeletedLine",
"lineBefore": 193,
"content": "\t\t\t\t\tdata: {"
},
{
"type": "DeletedLine",
"lineBefore": 194,
"content": "\t\t\t\t\t\tid,"
},
{
"type": "DeletedLine",
"lineBefore": 195,
"content": "\t\t\t\t\t\tfilepath,"
},
{
"type": "DeletedLine",
"lineBefore": 196,
"content": "\t\t\t\t\t\taltText: image.altText,"
},
{
"type": "DeletedLine",
"lineBefore": 197,
"content": "\t\t\t\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 198,
"content": "\t\t\t\t})"
},
{
"type": "DeletedLine",
"lineBefore": 199,
"content": "\t\t\t} else if (image.file) {"
},
{
"type": "DeletedLine",
"lineBefore": 200,
"content": "\t\t\t\tif (image.file.size < 1) return null"
},
{
"type": "DeletedLine",
"lineBefore": 201,
"content": "\t\t\t\tconst filepath = await writeImage(image.file)"
},
{
"type": "DeletedLine",
"lineBefore": 202,
"content": "\t\t\t\treturn db.image.create({"
},
{
"type": "DeletedLine",
"lineBefore": 203,
"content": "\t\t\t\t\taltText: image.altText,"
},
{
"type": "DeletedLine",
"lineBefore": 204,
"content": "\t\t\t\t\tfilepath,"
},
{
"type": "DeletedLine",
"lineBefore": 205,
"content": "\t\t\t\t\tcontentType: image.file.type,"
},
{
"type": "DeletedLine",
"lineBefore": 206,
"content": "\t\t\t\t})"
},
{
"type": "DeletedLine",
"lineBefore": 207,
"content": "\t\t\t} else {"
},
{
"type": "DeletedLine",
"lineBefore": 208,
"content": "\t\t\t\treturn null"
},
{
"type": "DeletedLine",
"lineBefore": 209,
"content": "\t\t\t}"
},
{
"type": "DeletedLine",
"lineBefore": 210,
"content": "\t\t}) ?? []"
},
{
"type": "DeletedLine",
"lineBefore": 211,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 212,
"content": "\tconst noteImages = await Promise.all(noteImagePromises)"
},
{
"type": "DeletedLine",
"lineBefore": 213,
"content": "\tdb.note.update({"
},
{
"type": "DeletedLine",
"lineBefore": 214,
"content": "\t\twhere: { id: { equals: id } },"
},
{
"type": "DeletedLine",
"lineBefore": 215,
"content": "\t\tdata: {"
},
{
"type": "DeletedLine",
"lineBefore": 216,
"content": "\t\t\ttitle,"
},
{
"type": "DeletedLine",
"lineBefore": 217,
"content": "\t\t\tcontent,"
},
{
"type": "DeletedLine",
"lineBefore": 218,
"content": "\t\t\timages: noteImages.filter(Boolean),"
},
{
"type": "DeletedLine",
"lineBefore": 219,
"content": "\t\t},"
},
{
"type": "DeletedLine",
"lineBefore": 220,
"content": "\t})"
},
{
"type": "DeletedLine",
"lineBefore": 221,
"content": "}"
},
{
"type": "DeletedLine",
"lineBefore": 222,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 223,
"content": "async function writeImage(image: File) {"
},
{
"type": "DeletedLine",
"lineBefore": 224,
"content": "\tconst tmpDir = path.join(os.tmpdir(), 'epic-web', 'images')"
},
{
"type": "DeletedLine",
"lineBefore": 225,
"content": "\tawait fs.mkdir(tmpDir, { recursive: true })"
},
{
"type": "DeletedLine",
"lineBefore": 226,
"content": ""
},
{
"type": "DeletedLine",
"lineBefore": 227,
"content": "\tconst timestamp = Date.now()"
},
{
"type": "DeletedLine",
"lineBefore": 228,
"content": "\tconst filepath = path.join(tmpDir, `${timestamp}.${image.name}`)"
},
{
"type": "DeletedLine",
"lineBefore": 229,
"content": "\tawait fs.writeFile(filepath, Buffer.from(await image.arrayBuffer()))"
},
{
"type": "DeletedLine",
"lineBefore": 230,
"content": "\treturn filepath"
},
{
"type": "DeletedLine",
"lineBefore": 231,
"content": "}"
},
{
"type": "AddedLine",
"lineAfter": 10,
"content": "export { prisma }"
}
]
}
],
"path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/kcdshop/diff/data-modeling/exercises__sep__09.finished__sep__01.problem/x9u3yksf6f/app/utils/db.server.ts"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment