Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions demos/bookstore/app/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ describe('account handlers', () => {
})

it('GET /account returns account page when authenticated', async () => {
let sessionId = await loginAsCustomer(router)
let sessionCookie = await loginAsCustomer(router)

// Now access account page with session
let request = requestWithSession('http://localhost:3000/account', sessionId)
let request = requestWithSession('http://localhost:3000/account', sessionCookie)
let response = await router.fetch(request)

assert.equal(response.status, 200)
Expand All @@ -27,10 +27,10 @@ describe('account handlers', () => {
})

it('GET /account/orders/:orderId shows order for authenticated user', async () => {
let sessionId = await loginAsCustomer(router)
let sessionCookie = await loginAsCustomer(router)

// Access existing order
let request = requestWithSession('http://localhost:3000/account/orders/1001', sessionId)
let request = requestWithSession('http://localhost:3000/account/orders/1001', sessionCookie)
let response = await router.fetch(request)

assert.equal(response.status, 200)
Expand Down
4 changes: 2 additions & 2 deletions demos/bookstore/app/admin.books.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { loginAsAdmin, requestWithSession } from '../test/helpers.ts'

describe('admin books handlers', () => {
it('POST /admin/books creates new book when admin', async () => {
let sessionId = await loginAsAdmin(router)
let sessionCookie = await loginAsAdmin(router)

// Create new book
let createRequest = requestWithSession('http://localhost:3000/admin/books', sessionId, {
let createRequest = requestWithSession('http://localhost:3000/admin/books', sessionCookie, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Expand Down
4 changes: 2 additions & 2 deletions demos/bookstore/app/admin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ describe('admin handlers', () => {
})

it('GET /admin returns 403 for non-admin users', async () => {
let sessionId = await loginAsCustomer(router)
let sessionCookie = await loginAsCustomer(router)

// Try to access admin
let request = requestWithSession('http://localhost:3000/admin', sessionId)
let request = requestWithSession('http://localhost:3000/admin', sessionCookie)
let response = await router.fetch(request)

assert.equal(response.status, 403)
Expand Down
10 changes: 5 additions & 5 deletions demos/bookstore/app/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as assert from 'node:assert/strict'
import { describe, it } from 'node:test'

import { router } from './router.ts'
import { getSessionCookie, assertContains } from '../test/helpers.ts'
import { assertContains, getSessionCookie } from '../test/helpers.ts'

describe('auth handlers', () => {
it('POST /login with valid credentials sets session cookie and redirects', async () => {
Expand All @@ -18,8 +18,8 @@ describe('auth handlers', () => {
assert.equal(response.status, 302)
assert.equal(response.headers.get('Location'), '/account')

let sessionId = getSessionCookie(response)
assert.ok(sessionId, 'Expected session cookie to be set')
let sessionCookie = getSessionCookie(response)
assert.ok(sessionCookie, 'Expected session cookie to be set')
})

it('POST /login with invalid credentials returns 401', async () => {
Expand Down Expand Up @@ -52,7 +52,7 @@ describe('auth handlers', () => {
assert.equal(response.status, 302)
assert.equal(response.headers.get('Location'), '/account')

let sessionId = getSessionCookie(response)
assert.ok(sessionId, 'Expected session cookie to be set')
let sessionCookie = getSessionCookie(response)
assert.ok(sessionCookie, 'Expected session cookie to be set')
})
})
27 changes: 9 additions & 18 deletions demos/bookstore/app/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { RouteHandlers } from '@remix-run/fetch-router'
import { redirect } from '@remix-run/fetch-router/response-helpers'

import { routes } from '../routes.ts'
import { getSession, setSessionCookie, login, logout } from './utils/session.ts'
import { login, logout } from './utils/session.ts'
import {
authenticateUser,
createUser,
Expand Down Expand Up @@ -64,7 +64,7 @@ export default {
)
},

async action({ request, formData }) {
async action({ session, formData }) {
let email = formData.get('email')?.toString() ?? ''
let password = formData.get('password')?.toString() ?? ''
let user = authenticateUser(email, password)
Expand All @@ -85,13 +85,9 @@ export default {
)
}

let session = getSession(request)
login(session.sessionId, user)
login(session, user)

let headers = new Headers()
setSessionCookie(headers, session.sessionId)

return redirect(routes.account.index.href(), { headers })
return redirect(routes.account.index.href())
},
},

Expand Down Expand Up @@ -136,7 +132,7 @@ export default {
)
},

async action({ request, formData }) {
async action({ session, formData }) {
let name = formData.get('name')?.toString() ?? ''
let email = formData.get('email')?.toString() ?? ''
let password = formData.get('password')?.toString() ?? ''
Expand Down Expand Up @@ -167,19 +163,14 @@ export default {

let user = createUser(email, password, name)

let session = getSession(request)
login(session.sessionId, user)

let headers = new Headers()
setSessionCookie(headers, session.sessionId)
login(session, user)

return redirect(routes.account.index.href(), { headers })
return redirect(routes.account.index.href())
},
},

logout({ request }) {
let session = getSession(request)
logout(session.sessionId)
logout({ session }) {
logout(session)

return redirect(routes.home.href())
},
Expand Down
52 changes: 38 additions & 14 deletions demos/bookstore/app/cart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import * as assert from 'node:assert/strict'
import { describe, it } from 'node:test'

import { router } from './router.ts'
import { getSessionCookie, requestWithSession, assertContains } from '../test/helpers.ts'
import {
requestWithSession,
assertContains,
loginAsCustomer,
assertNotContains,
getSessionCookie,
} from '../test/helpers.ts'

describe('cart handlers', () => {
it('POST /cart/api/add adds book to cart', async () => {
Expand All @@ -20,55 +26,73 @@ describe('cart handlers', () => {
})

it('GET /cart shows cart items', async () => {
let sessionCookie = await loginAsCustomer(router)

let request = requestWithSession('http://localhost:3000/cart', sessionCookie)
let response = await router.fetch(request)

assert.equal(response.status, 200)
let html = await response.text()
assertContains(html, 'Shopping Cart')
assertNotContains(html, 'Heavy Metal Guitar Riffs')

// First, add item to cart to get a session
let addResponse = await router.fetch('http://localhost:3000/cart/api/add', {
response = await router.fetch('http://localhost:3000/cart/api/add', {
method: 'POST',
body: new URLSearchParams({
bookId: '002',
slug: 'heavy-metal',
}),
headers: {
Cookie: sessionCookie,
},
redirect: 'manual',
})

let sessionId = getSessionCookie(addResponse)
assert.ok(sessionId)
sessionCookie = getSessionCookie(response)!

// Now view cart with session
let request = requestWithSession('http://localhost:3000/cart', sessionId)
let response = await router.fetch(request)
request = requestWithSession('http://localhost:3000/cart', sessionCookie)
response = await router.fetch(request)

assert.equal(response.status, 200)
let html = await response.text()
html = await response.text()
assertContains(html, 'Shopping Cart')
assertContains(html, 'Heavy Metal Guitar Riffs')
})

it('cart persists state across requests with same session', async () => {
let sessionCookie = await loginAsCustomer(router)

// Add first item
let addResponse1 = await router.fetch('http://localhost:3000/cart/api/add', {
let response = await router.fetch('http://localhost:3000/cart/api/add', {
method: 'POST',
body: new URLSearchParams({
bookId: '001',
slug: 'bbq',
}),
headers: {
Cookie: sessionCookie,
},
redirect: 'manual',
})

let sessionId = getSessionCookie(addResponse1)
assert.ok(sessionId)
sessionCookie = getSessionCookie(response)!

// Add second item with same session
let addRequest2 = requestWithSession('http://localhost:3000/cart/api/add', sessionId, {
let addRequest2 = requestWithSession('http://localhost:3000/cart/api/add', sessionCookie, {
method: 'POST',
body: new URLSearchParams({
bookId: '003',
slug: 'three-ways',
}),
headers: {
Cookie: sessionCookie,
},
redirect: 'manual',
})
await router.fetch(addRequest2)

// View cart - should have both items
let cartRequest = requestWithSession('http://localhost:3000/cart', sessionId)
let cartRequest = requestWithSession('http://localhost:3000/cart', sessionCookie)
let cartResponse = await router.fetch(cartRequest)

let html = await cartResponse.text()
Expand Down
95 changes: 43 additions & 52 deletions demos/bookstore/app/cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import { redirect } from '@remix-run/fetch-router/response-helpers'
import { routes } from '../routes.ts'

import { Layout } from './layout.tsx'
import { loadAuth, SESSION_ID_KEY } from './middleware/auth.ts'
import { loadAuth } from './middleware/auth.ts'
import { getBookById } from './models/books.ts'
import { getCart, addToCart, updateCartItem, removeFromCart, getCartTotal } from './models/cart.ts'
import type { User } from './models/users.ts'
import { getCurrentUser, getStorage } from './utils/context.ts'
import { getCurrentUser } from './utils/context.ts'
import { render } from './utils/render.ts'
import { setSessionCookie } from './utils/session.ts'
import { RestfulForm } from './components/restful-form.tsx'
import { ensureCart } from './middleware/cart.ts'

export default {
use: [loadAuth],
handlers: {
index() {
let sessionId = getStorage().get(SESSION_ID_KEY)
let cart = getCart(sessionId)
let total = getCartTotal(cart)
index({ session }) {
let cartId = session.get('cartId')
let cart = cartId ? getCart(cartId) : null
let total = cart ? getCartTotal(cart) : 0

let user: User | null = null
try {
Expand All @@ -33,7 +33,7 @@ export default {
<h1>Shopping Cart</h1>

<div class="card">
{cart.items.length > 0 ? (
{cart && cart.items.length > 0 ? (
<>
<table>
<thead>
Expand Down Expand Up @@ -137,64 +137,55 @@ export default {
},

api: {
async add({ storage, formData }) {
// Simulate network latency
await new Promise((resolve) => setTimeout(resolve, 1000))
use: [ensureCart],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cart middleware that runs before all cart API handlers

handlers: {
async add({ session, formData }) {
// Simulate network latency
await new Promise((resolve) => setTimeout(resolve, 1000))

let sessionId = storage.get(SESSION_ID_KEY)
let bookId = formData.get('bookId')?.toString() ?? ''
let bookId = formData.get('bookId')?.toString() ?? ''

let book = getBookById(bookId)
if (!book) {
return new Response('Book not found', { status: 404 })
}
let book = getBookById(bookId)
if (!book) {
return new Response('Book not found', { status: 404 })
}

addToCart(sessionId, book.id, book.slug, book.title, book.price, 1)
addToCart(session.get('cartId')!, book.id, book.slug, book.title, book.price, 1)

let headers = new Headers()
setSessionCookie(headers, sessionId)
if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}

if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}
return redirect(routes.cart.index.href())
},

return redirect(routes.cart.index.href(), { headers })
},

async update({ storage, formData }) {
let sessionId = storage.get(SESSION_ID_KEY)
let bookId = formData.get('bookId')?.toString() ?? ''
let quantity = parseInt(formData.get('quantity')?.toString() ?? '1', 10)

updateCartItem(sessionId, bookId, quantity)
async update({ session, formData }) {
let bookId = formData.get('bookId')?.toString() ?? ''
let quantity = parseInt(formData.get('quantity')?.toString() ?? '1', 10)

let headers = new Headers()
setSessionCookie(headers, sessionId)
updateCartItem(session.get('cartId')!, bookId, quantity)

if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}

return redirect(routes.cart.index.href(), { headers })
},
if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}

async remove({ storage, formData }) {
// Simulate network latency
await new Promise((resolve) => setTimeout(resolve, 1000))
return redirect(routes.cart.index.href())
},

let sessionId = storage.get(SESSION_ID_KEY)
let bookId = formData.get('bookId')?.toString() ?? ''
async remove({ session, formData }) {
// Simulate network latency
await new Promise((resolve) => setTimeout(resolve, 1000))

removeFromCart(sessionId, bookId)
let bookId = formData.get('bookId')?.toString() ?? ''

let headers = new Headers()
setSessionCookie(headers, sessionId)
removeFromCart(session.get('cartId')!, bookId)

if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}
if (formData.get('redirect') === 'none') {
return new Response(null, { status: 204 })
}

return redirect(routes.cart.index.href(), { headers })
return redirect(routes.cart.index.href())
},
},
},
},
Expand Down
Loading