Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ee6cfa7
Fix Playwright webServer command
ProchaLu Oct 9, 2024
fced86e
Merge branch 'main' into next
karlhorky Oct 9, 2024
454e16d
Merge branch 'main' into next
ProchaLu Oct 15, 2024
ec9f40a
Upgrade to Jest v30 alpha and esbuild-register for jest.config.ts (#5)
karlhorky Oct 15, 2024
331fb2b
Fix Fly.io config (#8)
karlhorky Oct 15, 2024
f688c58
Fix race condition in Jest test (#9)
karlhorky Oct 15, 2024
f961765
Use type parameter for deleteAnimalInsecure function (#2)
Eprince-hub Oct 15, 2024
57903c0
Merge branch 'main' into next
ProchaLu Oct 16, 2024
dcdc776
Merge branch 'main' into next
ProchaLu Oct 16, 2024
c415c5e
Merge branch 'main' into next
ProchaLu Oct 16, 2024
09a7634
Fix type error
karlhorky Oct 16, 2024
f001699
Merge branch 'main' into next
karlhorky Oct 24, 2024
6c6788d
Merge branch 'main' into next
karlhorky Nov 1, 2024
bedd750
Fix lockfile
karlhorky Nov 1, 2024
ecfa56f
Update lockfile
karlhorky Nov 1, 2024
fc0b375
Merge branch 'main' into next
karlhorky Nov 2, 2024
72c2cf2
Reorder and document cookie options (#10)
karlhorky Nov 3, 2024
db772cb
Merge branch 'main' of https://github.com/upleveled/next-js-example-f…
Eprince-hub Nov 4, 2024
8355ca9
Add better UI for different errors on note page (#11)
Eprince-hub Nov 4, 2024
ae70336
Update Next.js to version 15.0.3 and remove dynamicIO configuration (…
Eprince-hub Dec 17, 2024
1a7b79b
Bump next from 15.0.0-canary.171 to 15.1.0 (#14)
Eprince-hub Dec 17, 2024
198321d
Enhance CI/CD workflow to handle ignored build scripts in pnpm install
ProchaLu Feb 10, 2025
ebaaf96
Switch workaround script to strict-dep-builds setting
karlhorky Feb 11, 2025
bffdd00
Updates from editor
karlhorky Feb 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Fail on pnpm ignored build scripts
# - https://github.com/pnpm/pnpm/pull/9071
strict-dep-builds=true
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ type Props = {
};

export default async function DeleteAnimalNaivePage(props: Props) {
const animal = await deleteAnimalInsecure({
id: Number((await props.params).animalId),
});
const animal = await deleteAnimalInsecure(
Number((await props.params).animalId),
);

if (!animal) {
notFound();
Expand Down
4 changes: 1 addition & 3 deletions app/api/animals/[animalId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,7 @@ export async function DELETE(
): Promise<NextResponse<AnimalResponseBodyDelete>> {
console.log(Number((await params).animalId));

// const animal = await deleteAnimalInsecure({
// id: Number((await params).animalId),
// });
// const animal = await deleteAnimalInsecure(Number((await params).animalId));

const sessionTokenCookie = (await cookies()).get('sessionToken');

Expand Down
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const geistMono = localFont({
weight: '100 900',
});

export const dynamic = 'force-dynamic';

export const metadata = {
title: {
default: 'Home | UpLeveled',
Expand Down
25 changes: 19 additions & 6 deletions app/notes/[noteId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from 'next/link';
import { getNote } from '../../../database/notes';
import { getNote, selectNoteExists } from '../../../database/notes';
import { getCookie } from '../../../util/cookies';

type Props = {
Expand All @@ -9,15 +9,28 @@ type Props = {
};
export default async function NotePage({ params }: Props) {
// Task: Restrict access to the note page only to the user who created the note

const noteId = Number((await params).noteId);

// 1. Check if the sessionToken cookie exists
const sessionTokenCookie = await getCookie('sessionToken');

// 2. Query the notes with the session token and noteId
// 2. Check if the note exists
if (!(await selectNoteExists(noteId))) {
return (
<div>
<h1>Error loading note {noteId}</h1>
<div>The note does not exist</div>
<Link href="/notes">Back to notes</Link>
</div>
);
}

// 3. Query the notes with the session token and noteId
const note =
sessionTokenCookie &&
(await getNote(sessionTokenCookie, Number((await params).noteId)));
sessionTokenCookie && (await getNote(sessionTokenCookie, noteId));

// 3. If there is no note for the current user, show restricted access message
// 4. If there is no note for the current user, show restricted access message
if (!note) {
return (
<div>
Expand All @@ -28,7 +41,7 @@ export default async function NotePage({ params }: Props) {
);
}

// 4. Finally display the notes created by the current user
// 5. Finally display the notes created by the current user
return (
<div>
<h1>{note.title}</h1>
Expand Down
37 changes: 25 additions & 12 deletions database/animals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,29 @@ export const updateAnimalInsecure = cache(async (updatedAnimal: Animal) => {
return animal;
});

export const deleteAnimalInsecure = cache(
async (deletedAnimal: Pick<Animal, 'id'>) => {
const [animal] = await sql<Animal[]>`
DELETE FROM animals
WHERE
id = ${deletedAnimal.id}
RETURNING
animals.*
`;
export const deleteAnimalInsecure = cache(async (animalId: Animal['id']) => {
const [animal] = await sql<Animal[]>`
DELETE FROM animals
WHERE
id = ${animalId}
RETURNING
animals.*
`;

return animal;
},
);
return animal;
});

// Alternative: Using the TypeScript Pick utility type
// export const deleteAnimalInsecure = cache(
// async (deletedAnimal: Pick<Animal, 'id'>) => {
// const [animal] = await sql<Animal[]>`
// DELETE FROM animals
// WHERE
// id = ${deletedAnimal.id}
// RETURNING
// animals.*
// `;

// return animal;
// },
// );
16 changes: 16 additions & 0 deletions database/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ export const getNote = cache(async (sessionToken: string, noteId: number) => {
return note;
});

export async function selectNoteExists(noteId: Note['id']) {
const [record] = await sql<{ exists: boolean }[]>`
SELECT
EXISTS (
SELECT
TRUE
FROM
notes
WHERE
id = ${noteId}
)
`;

return Boolean(record?.exists);
}

export const createNote = cache(
async (
sessionToken: Session['token'],
Expand Down
4 changes: 2 additions & 2 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ primary_region = "otp"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
auto_stop_machines = "suspend"
auto_start_machines = true
6 changes: 4 additions & 2 deletions jest.config.js → jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/** @jest-config-loader esbuild-register */

import type { Config } from 'jest';
import nextJest from 'next/jest.js';

// https://nextjs.org/docs/app/building-your-application/testing/jest
const createJestConfig = nextJest({
dir: './',
});

/** @type {import('jest').Config} */
const config = {
const config: Config = {
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['<rootDir>/playwright/'],
};
Expand Down
1 change: 0 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
experimental: {
dynamicIO: true,
typedRoutes: true,
},
eslint: {
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"bcrypt": "^5.1.1",
"dayjs": "^1.11.13",
"dotenv-safe": "^9.1.0",
"next": "15.0.0-canary.171",
"next": "15.1.0",
"postgres": "^3.4.4",
"react": "19.0.0-rc-3edc000d-20240926",
"react-dom": "19.0.0-rc-3edc000d-20240926",
Expand All @@ -26,18 +26,19 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@jest/globals": "^30.0.0-alpha.6",
"@playwright/test": "^1.47.2",
"@ts-safeql/eslint-plugin": "^3.4.7",
"@types/bcrypt": "^5.0.2",
"@types/dotenv-safe": "^8.1.6",
"@types/node": "^22.7.4",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"esbuild-register": "^3.6.0",
"eslint": "^9.11.1",
"eslint-config-upleveled": "^8.8.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest": "^30.0.0-alpha.6",
"jest-environment-jsdom": "^30.0.0-alpha.6",
"libpg-query": "16.2.0",
"prettier": "^3.3.3",
"prettier-plugin-embed": "^0.4.15",
Expand Down
Loading
Loading