Got working preact SSR
parent
5142d4c551
commit
ddad7fe746
@ -1 +1,2 @@
|
||||
auto-install-peers=true
|
||||
# Needed by pnpm to work with "@preact/preset-vite"
|
||||
shamefully-hoist=true
|
||||
|
@ -1,13 +1,30 @@
|
||||
import Router from 'preact-router'
|
||||
import { route } from 'preact-router'
|
||||
import { useEffect } from 'preact/hooks'
|
||||
|
||||
import { HomePage } from './pages/Home.jsx'
|
||||
import { LoginPage } from './pages/Login.jsx'
|
||||
import { ProblemPage } from './pages/Problem.jsx'
|
||||
|
||||
export const App = ({ path }) => (
|
||||
<Router>
|
||||
<HomePage path="/" />
|
||||
<LoginPage path="/login" />
|
||||
<ProblemPage path="/problem/:id" />
|
||||
</Router>
|
||||
)
|
||||
const Redirect = ({ to }) => {
|
||||
useEffect(() => {
|
||||
route(to, true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
Redirecting to <pre>{to}</pre>...
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const App = ({ url }) => {
|
||||
return (
|
||||
<Router url={url}>
|
||||
<HomePage path="/" />
|
||||
<LoginPage path="/login" />
|
||||
<ProblemPage path="/problem/:id" />
|
||||
<Redirect default to="/" />
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
import { useSignal } from '@preact/signals'
|
||||
import { useEffect, useMemo } from 'preact/hooks'
|
||||
|
||||
import URLPattern from 'url-pattern'
|
||||
|
||||
export const Router = ({ pages }) => {
|
||||
const compiledRoutes = useMemo(
|
||||
() =>
|
||||
Object.entries(pages).map(([pattern, Page]) => ({
|
||||
pattern: new URLPattern(pattern),
|
||||
Page,
|
||||
})),
|
||||
[pages]
|
||||
)
|
||||
|
||||
const routerUrl = useSignal(location.hash.slice(1))
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('hashchange', () => {
|
||||
routerUrl.value = location.hash.slice(1)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const route = compiledRoutes.flatMap(({ pattern, Page }) => {
|
||||
const m = pattern.match(routerUrl.value.split('?', 1)[0])
|
||||
return m ? [{ Page, params: m }] : []
|
||||
})?.[0]
|
||||
|
||||
if (!route) {
|
||||
console.log(`Invalid route "${routerUrl.value}", redirecting to homepage`)
|
||||
|
||||
location.href = '/#/'
|
||||
routerUrl.value = '/'
|
||||
|
||||
return <>Redirecting...</>
|
||||
}
|
||||
|
||||
const { Page, params } = route
|
||||
|
||||
const queryPart =
|
||||
routerUrl.value.indexOf('?') === -1
|
||||
? ''
|
||||
: routerUrl.value.slice(routerUrl.value.indexOf('?') + 1)
|
||||
console.log(queryPart)
|
||||
|
||||
const queryParams =
|
||||
queryPart?.length > 0
|
||||
? Object.fromEntries(
|
||||
queryPart.split('&').map(kvPart => {
|
||||
const eqIndex = kvPart.indexOf('=')
|
||||
return eqIndex === -1
|
||||
? [kvPart, true]
|
||||
: [
|
||||
kvPart.slice(0, eqIndex),
|
||||
decodeURIComponent(kvPart.slice(eqIndex + 1)),
|
||||
]
|
||||
})
|
||||
)
|
||||
: {}
|
||||
|
||||
return <Page params={params} query={queryParams} />
|
||||
}
|
||||
|
||||
export const Link = ({ page, params, query, children }) => {
|
||||
for (const [key, value] of Object.entries(params ?? {})) {
|
||||
page = page.replace(':' + key, encodeURIComponent(value))
|
||||
}
|
||||
|
||||
let targetHref = page
|
||||
if (query) {
|
||||
targetHref +=
|
||||
'?' +
|
||||
Object.entries(query ?? {})
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
return <a href={'/#' + targetHref}>{children}</a>
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { hydrate } from 'preact'
|
||||
import { App } from './App.jsx'
|
||||
|
||||
hydrate(<App />, document.body)
|
@ -0,0 +1,6 @@
|
||||
import renderToString from 'preact-render-to-string'
|
||||
import { App } from './App.jsx'
|
||||
|
||||
export function render(url) {
|
||||
return renderToString(<App url={url} />)
|
||||
}
|
@ -1,15 +1,24 @@
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
|
||||
export const useUser = () => {
|
||||
const [username, setUsername] = useState(null)
|
||||
const [user, setUser] = useState(null)
|
||||
|
||||
const logout = () => {
|
||||
setUser(null)
|
||||
}
|
||||
|
||||
useEffect(async () => {
|
||||
const res = await fetch(`/api/current-user`, {
|
||||
credentials: 'include',
|
||||
})
|
||||
const username = await res.json()
|
||||
setUsername(username)
|
||||
|
||||
if (res.ok) {
|
||||
const user = await res.json()
|
||||
setUser(user)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { username }
|
||||
console.log(user)
|
||||
|
||||
return [user, logout]
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
export const initialDatabaseValue = {
|
||||
users: {
|
||||
['BachoSeven']: {},
|
||||
['aziis98']: {},
|
||||
},
|
||||
problems: {},
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
import express from 'express'
|
||||
|
||||
import crypto from 'crypto'
|
||||
|
||||
// import serveStatic from 'serve-static'
|
||||
import bodyParser from 'body-parser'
|
||||
import cookieParser from 'cookie-parser'
|
||||
|
||||
import { authMiddleware, loggingMiddleware, PingRouter, StatusRouter } from './server/routes.js'
|
||||
import { createDatabase, getUser, updateUser } from './server/db/database.js'
|
||||
|
||||
const app = express()
|
||||
|
||||
const sessions = {
|
||||
store: {},
|
||||
createSession(username) {
|
||||
const sid = crypto.randomBytes(10).toString('hex')
|
||||
this.store[sid] = username
|
||||
return sid
|
||||
},
|
||||
getUserForSession(sid) {
|
||||
return this.store[sid] ?? null
|
||||
},
|
||||
}
|
||||
|
||||
const db = createDatabase('./db.local.json', {
|
||||
users: {
|
||||
['BachoSeven']: {},
|
||||
['aziis98']: {},
|
||||
},
|
||||
problems: {},
|
||||
})
|
||||
|
||||
app.use(bodyParser.json())
|
||||
app.use(cookieParser())
|
||||
|
||||
app.use(loggingMiddleware)
|
||||
app.use(authMiddleware(sid => sessions.getUserForSession(sid)))
|
||||
|
||||
app.use('/api/status', new StatusRouter())
|
||||
app.use('/api/ping', new PingRouter())
|
||||
|
||||
app.get('/api/current-user', (req, res) => {
|
||||
res.json(sessions.getUserForSession(req.cookies.sid))
|
||||
})
|
||||
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username } = req.body
|
||||
res.cookie('sid', sessions.createSession(username), { maxAge: 1000 * 60 * 60 * 24 * 7 })
|
||||
res.json({ status: 'ok' })
|
||||
})
|
||||
|
||||
app.post('/api/logout', (req, res) => {
|
||||
res.cookie('sid', '', { expires: new Date() })
|
||||
res.json({ status: 'ok' })
|
||||
})
|
||||
|
||||
app.get('/api/user/:id', async (req, res) => {
|
||||
const user = await getUser(db, req.params.id)
|
||||
|
||||
if (user) {
|
||||
res.json(user)
|
||||
} else {
|
||||
res.sendStatus(404)
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/user/:id', async (req, res) => {
|
||||
await updateUser(db, req.params.id, req.body)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
app.all('*', (_req, res) => {
|
||||
res.sendStatus(404)
|
||||
})
|
||||
|
||||
app.listen(4000, () => {
|
||||
console.log(`Started server on port 4000...`)
|
||||
})
|
@ -0,0 +1,86 @@
|
||||
import crypto from 'crypto'
|
||||
|
||||
import bodyParser from 'body-parser'
|
||||
import cookieParser from 'cookie-parser'
|
||||
|
||||
import { authMiddleware, loggingMiddleware, PingRouter, StatusRouter } from './middlewares.js'
|
||||
import { createDatabase, getUser, updateUser } from './db/database.js'
|
||||
import express from 'express'
|
||||
import { initialDatabaseValue } from './db/example-data.js'
|
||||
import { useId } from 'preact/hooks'
|
||||
|
||||
export async function createApiRouter() {
|
||||
const sessions = {
|
||||
store: {},
|
||||
createSession(username) {
|
||||
const sid = crypto.randomBytes(10).toString('hex')
|
||||
this.store[sid] = username
|
||||
return sid
|
||||
},
|
||||
getUserForSession(sid) {
|
||||
return this.store[sid] ?? null
|
||||
},
|
||||
}
|
||||
|
||||
const db = createDatabase('./db.local.json', initialDatabaseValue)
|
||||
|
||||
const r = express.Router()
|
||||
|
||||
r.use(bodyParser.json())
|
||||
r.use(cookieParser())
|
||||
|
||||
r.use(loggingMiddleware)
|
||||
r.use(authMiddleware(sid => sessions.getUserForSession(sid)))
|
||||
|
||||
r.use('/api/status', new StatusRouter())
|
||||
r.use('/api/ping', new PingRouter())
|
||||
|
||||
r.get('/api/current-user', async (req, res) => {
|
||||
const userId = sessions.getUserForSession(req.cookies.sid)
|
||||
if (!userId) {
|
||||
res.cookie('sid', '', { expires: new Date() })
|
||||
res.status(400)
|
||||
res.end('Invalid session token')
|
||||
return
|
||||
}
|
||||
|
||||
const user = await getUser(db, userId)
|
||||
|
||||
res.json({
|
||||
username: userId,
|
||||
...user,
|
||||
})
|
||||
})
|
||||
|
||||
r.post('/api/login', (req, res) => {
|
||||
const { username } = req.body
|
||||
res.cookie('sid', sessions.createSession(username), { maxAge: 1000 * 60 * 60 * 24 * 7 })
|
||||
res.json({ status: 'ok' })
|
||||
})
|
||||
|
||||
r.post('/api/logout', (req, res) => {
|
||||
res.cookie('sid', '', { expires: new Date() })
|
||||
res.json({ status: 'ok' })
|
||||
})
|
||||
|
||||
r.get('/api/user/:id', async (req, res) => {
|
||||
const user = await getUser(db, req.params.id)
|
||||
|
||||
if (user) {
|
||||
res.json(user)
|
||||
} else {
|
||||
res.sendStatus(404)
|
||||
}
|
||||
})
|
||||
|
||||
r.post('/api/user/:id', async (req, res) => {
|
||||
await updateUser(db, req.params.id, req.body)
|
||||
res.sendStatus(200)
|
||||
})
|
||||
|
||||
// r.all('*', (_req, res) => {
|
||||
// res.sendStatus(404)
|
||||
// })
|
||||
|
||||
return r
|
||||
}
|
Loading…
Reference in New Issue