diff options
author | 2023-05-03 17:40:47 +0100 | |
---|---|---|
committer | 2023-05-03 17:40:47 +0100 | |
commit | 831b67cdb8250f93f66e3b171fab024652bf80f2 (patch) | |
tree | f2b32803f20b581985ca29af960cdc03835f8848 /examples/middleware/src | |
parent | ad907196cb42f21d9540ae0d77aa742bf7adf030 (diff) | |
download | astro-831b67cdb8250f93f66e3b171fab024652bf80f2.tar.gz astro-831b67cdb8250f93f66e3b171fab024652bf80f2.tar.zst astro-831b67cdb8250f93f66e3b171fab024652bf80f2.zip |
feat(astro): experimental middleware (#6721)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Diffstat (limited to 'examples/middleware/src')
-rw-r--r-- | examples/middleware/src/components/Card.astro | 63 | ||||
-rw-r--r-- | examples/middleware/src/env.d.ts | 13 | ||||
-rw-r--r-- | examples/middleware/src/layouts/Layout.astro | 35 | ||||
-rw-r--r-- | examples/middleware/src/middleware.ts | 71 | ||||
-rw-r--r-- | examples/middleware/src/pages/admin.astro | 55 | ||||
-rw-r--r-- | examples/middleware/src/pages/api/login.ts | 18 | ||||
-rw-r--r-- | examples/middleware/src/pages/index.astro | 63 | ||||
-rw-r--r-- | examples/middleware/src/pages/login.astro | 75 |
8 files changed, 393 insertions, 0 deletions
diff --git a/examples/middleware/src/components/Card.astro b/examples/middleware/src/components/Card.astro new file mode 100644 index 000000000..c68fa2ab3 --- /dev/null +++ b/examples/middleware/src/components/Card.astro @@ -0,0 +1,63 @@ +--- +export interface Props { + title: string; + body: string; + href: string; +} + +const { href, title, body } = Astro.props; +--- + +<li class="link-card"> + <a href={href}> + <h2> + {title} + <span>→</span> + </h2> + <p> + {body} + </p> + </a> +</li> +<style> + .link-card { + list-style: none; + display: flex; + padding: 0.25rem; + background-color: white; + background-image: none; + background-size: 400%; + border-radius: 0.6rem; + background-position: 100%; + transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); + } + + .link-card > a { + width: 100%; + text-decoration: none; + line-height: 1.4; + padding: 1rem 1.3rem; + border-radius: 0.35rem; + color: #111; + background-color: white; + opacity: 0.8; + } + h2 { + margin: 0; + font-size: 1.25rem; + transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1); + } + p { + margin-top: 0.5rem; + margin-bottom: 0; + color: #444; + } + .link-card:is(:hover, :focus-within) { + background-position: 0; + background-image: var(--accent-gradient); + } + .link-card:is(:hover, :focus-within) h2 { + color: rgb(var(--accent)); + } +</style> diff --git a/examples/middleware/src/env.d.ts b/examples/middleware/src/env.d.ts new file mode 100644 index 000000000..f2de6d45d --- /dev/null +++ b/examples/middleware/src/env.d.ts @@ -0,0 +1,13 @@ +/// <reference types="astro/client" /> +declare global { + namespace AstroMiddleware { + interface Locals { + user: { + name: string; + surname: string; + }; + } + } +} + +export {}; diff --git a/examples/middleware/src/layouts/Layout.astro b/examples/middleware/src/layouts/Layout.astro new file mode 100644 index 000000000..22100824e --- /dev/null +++ b/examples/middleware/src/layouts/Layout.astro @@ -0,0 +1,35 @@ +--- +export interface Props { + title: string; +} + +const { title } = Astro.props; +--- + +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width" /> + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> + <meta name="generator" content={Astro.generator} /> + <title>{title}</title> + </head> + <body> + <slot /> + </body> +</html> +<style is:global> + :root { + --accent: 124, 58, 237; + --accent-gradient: linear-gradient(45deg, rgb(var(--accent)), #da62c4 30%, white 60%); + } + html { + font-family: system-ui, sans-serif; + background-color: #f6f6f6; + } + code { + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; + } +</style> diff --git a/examples/middleware/src/middleware.ts b/examples/middleware/src/middleware.ts new file mode 100644 index 000000000..1c0bd855f --- /dev/null +++ b/examples/middleware/src/middleware.ts @@ -0,0 +1,71 @@ +import { defineMiddleware, sequence } from 'astro/middleware'; +import htmlMinifier from 'html-minifier'; + +const limit = 50; + +const loginInfo = { + token: undefined, + currentTime: undefined, +}; + +export const minifier = defineMiddleware(async (context, next) => { + const response = await next(); + // check if the response is returning some HTML + if (response.headers.get('content-type') === 'text/html') { + let headers = response.headers; + let html = await response.text(); + let newHtml = htmlMinifier.minify(html, { + removeAttributeQuotes: true, + collapseWhitespace: true, + }); + return new Response(newHtml, { + status: 200, + headers, + }); + } + return response; +}); + +const validation = defineMiddleware(async (context, next) => { + if (context.request.url.endsWith('/admin')) { + if (loginInfo.currentTime) { + const difference = new Date().getTime() - loginInfo.currentTime; + if (difference > limit) { + console.log('hit threshold'); + loginInfo.token = undefined; + loginInfo.currentTime = undefined; + return context.redirect('/login'); + } + } + // we naively check if we have a token + if (loginInfo.token && loginInfo.token === 'loggedIn') { + // we fill the locals with user-facing information + context.locals.user = { + name: 'AstroUser', + surname: 'AstroSurname', + }; + return await next(); + } else { + loginInfo.token = undefined; + loginInfo.currentTime = undefined; + return context.redirect('/login'); + } + } else if (context.request.url.endsWith('/api/login')) { + const response = await next(); + // the login endpoint will return to us a JSON with username and password + const data = await response.json(); + // we naively check if username and password are equals to some string + if (data.username === 'astro' && data.password === 'astro') { + // we store the token somewhere outside of locals because the `locals` object is attached to the request + // and when doing a redirect, we lose that information + loginInfo.token = 'loggedIn'; + loginInfo.currentTime = new Date().getTime(); + return context.redirect('/admin'); + } + } + // we don't really care about awaiting the response in this case + next(); + return; +}); + +export const onRequest = sequence(validation, minifier); diff --git a/examples/middleware/src/pages/admin.astro b/examples/middleware/src/pages/admin.astro new file mode 100644 index 000000000..028fd6b08 --- /dev/null +++ b/examples/middleware/src/pages/admin.astro @@ -0,0 +1,55 @@ +--- +import Layout from '../layouts/Layout.astro'; +const user = Astro.locals.user; +--- + +<Layout title="Welcome back!!"> + <main> + <h1>Welcome back <span class="text-gradient">{user.name} {user.surname}</span></h1> + </main> +</Layout> + +<style> + main { + margin: auto; + padding: 1.5rem; + max-width: 60ch; + } + h1 { + font-size: 3rem; + font-weight: 800; + margin: 0; + } + .text-gradient { + background-image: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-size: 400%; + background-position: 0%; + } + .instructions { + line-height: 1.6; + margin: 1rem 0; + border: 1px solid rgba(var(--accent), 25%); + background-color: white; + padding: 1rem; + border-radius: 0.4rem; + } + .instructions code { + font-size: 0.875em; + font-weight: bold; + background: rgba(var(--accent), 12%); + color: rgb(var(--accent)); + border-radius: 4px; + padding: 0.3em 0.45em; + } + .instructions strong { + color: rgb(var(--accent)); + } + .link-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr)); + gap: 1rem; + padding: 0; + } +</style> diff --git a/examples/middleware/src/pages/api/login.ts b/examples/middleware/src/pages/api/login.ts new file mode 100644 index 000000000..fa3f7b59b --- /dev/null +++ b/examples/middleware/src/pages/api/login.ts @@ -0,0 +1,18 @@ +import { APIRoute } from 'astro'; + +export const post: APIRoute = async ({ request }) => { + const data = await request.formData(); + const username = data.get('username'); + const password = data.get('password'); + return new Response( + JSON.stringify({ + username, + password, + }), + { + headers: { + 'content-type': 'application/json', + }, + } + ); +}; diff --git a/examples/middleware/src/pages/index.astro b/examples/middleware/src/pages/index.astro new file mode 100644 index 000000000..ff77d4a15 --- /dev/null +++ b/examples/middleware/src/pages/index.astro @@ -0,0 +1,63 @@ +--- +import Layout from '../layouts/Layout.astro'; +import Card from '../components/Card.astro'; +--- + +<Layout title="Welcome to Astro."> + <main> + <h1>Welcome to <span class="text-gradient">Astro</span></h1> + <p class="instructions"> + To get started, open the directory <code>src/pages</code> in your project.<br /> + <strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above. + </p> + {} + <ul role="list" class="link-card-grid"> + <Card href="/login" title="Login" body="Try the login" /> + </ul> + </main> +</Layout> + +<style> + main { + margin: auto; + padding: 1.5rem; + max-width: 60ch; + } + h1 { + font-size: 3rem; + font-weight: 800; + margin: 0; + } + .text-gradient { + background-image: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-size: 400%; + background-position: 0%; + } + .instructions { + line-height: 1.6; + margin: 1rem 0; + border: 1px solid rgba(var(--accent), 25%); + background-color: white; + padding: 1rem; + border-radius: 0.4rem; + } + .instructions code { + font-size: 0.875em; + font-weight: bold; + background: rgba(var(--accent), 12%); + color: rgb(var(--accent)); + border-radius: 4px; + padding: 0.3em 0.45em; + } + .instructions strong { + color: rgb(var(--accent)); + } + .link-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr)); + gap: 1rem; + padding: 0; + } +</style> diff --git a/examples/middleware/src/pages/login.astro b/examples/middleware/src/pages/login.astro new file mode 100644 index 000000000..99cf4cc94 --- /dev/null +++ b/examples/middleware/src/pages/login.astro @@ -0,0 +1,75 @@ +--- +import Layout from '../layouts/Layout.astro'; + +const status = Astro.response.status; +let redirectMessage; +if (status === 301) { + redirectMessage = 'Your session is finished, please login again'; +} +--- + +<Layout title="Welcome to Astro."> + <main> + <h1>Welcome to <span class="text-gradient">Astro</span></h1> + <p class="instructions"> + To get started, open the directory <code>src/pages</code> in your project.<br /> + <strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above. + </p> + {redirectMessage} + <form action="/api/login" method="POST"> + <label> + Username: <input type="text" minlength="1" id="username" name="username" /> + </label> + <label> + Password: <input type="password" minlength="1" id="password" name="password" /> + </label> + + <button>Submit</button> + </form> + </main> +</Layout> + +<style> + main { + margin: auto; + padding: 1.5rem; + max-width: 60ch; + } + h1 { + font-size: 3rem; + font-weight: 800; + margin: 0; + } + .text-gradient { + background-image: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-size: 400%; + background-position: 0%; + } + .instructions { + line-height: 1.6; + margin: 1rem 0; + border: 1px solid rgba(var(--accent), 25%); + background-color: white; + padding: 1rem; + border-radius: 0.4rem; + } + .instructions code { + font-size: 0.875em; + font-weight: bold; + background: rgba(var(--accent), 12%); + color: rgb(var(--accent)); + border-radius: 4px; + padding: 0.3em 0.45em; + } + .instructions strong { + color: rgb(var(--accent)); + } + .link-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr)); + gap: 1rem; + padding: 0; + } +</style> |