fix: added BASE_URL support

pull/1/head
Antonio De Lucreziis 2 years ago
parent 69b1601d92
commit 49632f1e2c

@ -0,0 +1,3 @@
BASE_URL=/
DATABASE_PATH=db.local.json

1
.gitignore vendored

@ -6,6 +6,7 @@ node_modules
dist dist
# Local files # Local files
.env
*.local* *.local*
# Editor directories and files # Editor directories and files

@ -7,6 +7,7 @@ Sito con una bacheca di problemi e la possibilità di inviare soluzioni in markd
Installare tutte le dipendenze con il package manager preferito di turno (ed in tal caso sostituire `npm` con ad esempio `pnpm` o `yarn`) Installare tutte le dipendenze con il package manager preferito di turno (ed in tal caso sostituire `npm` con ad esempio `pnpm` o `yarn`)
```bash shell ```bash shell
$ cp .env.development .env
$ npm install $ npm install
``` ```
@ -25,38 +26,4 @@ $ npm run serve
# TODO # TODO
- Pagina profilo utente - [ ] Autenticazione vera magari con OAuth dell'ateneo o Google
- Lista soluzioni inviate (con stato delle soluzioni: approvate o rifiutate)
- Pagina dell'admin
- Lista delle soluzioni non corrette
- Creazione nuovi problemi
- DBv2
```js
relations: [
{
from: 'Solution'
name: 'for'
to: 'Problem'
entries: [
['LRLAH2NoLFQAQHQgz', '1']
['JzyiDnwRCrkpzLL8W', '1']
['FFYMJjP2yr4ohdmdT', '2']
['VFHTb8fSrLOkPNVFx', '2']
]
}
{
from: 'User'
name: 'owns'
to: 'Solution'
entries: [
['aziis98', 'LRLAH2NoLFQAQHQgz']
['aziis98', 'JzyiDnwRCrkpzLL8W']
['BachoSeven', 'FFYMJjP2yr4ohdmdT']
['BachoSeven', 'VFHTb8fSrLOkPNVFx']
]
}
]
```

@ -1,10 +1,14 @@
export function prependBaseUrl(route: string) {
return import.meta.env.BASE_URL + (route.startsWith('/') ? route.substring(1) : route)
}
export const server = { export const server = {
async get(url: string) { async get(url: string) {
const res = await fetch(url, { credentials: 'include' }) const res = await fetch(prependBaseUrl(url), { credentials: 'include' })
return await res.json() return await res.json()
}, },
async post<T>(url: string, body?: T) { async post<T>(url: string, body?: T) {
const res = await fetch(url, { const res = await fetch(prependBaseUrl(url), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
@ -16,7 +20,7 @@ export const server = {
return await res.json() return await res.json()
}, },
async patch<T>(url: string, body?: T) { async patch<T>(url: string, body?: T) {
const res = await fetch(url, { const res = await fetch(prependBaseUrl(url), {
method: 'PATCH', method: 'PATCH',
credentials: 'include', credentials: 'include',
headers: { headers: {

@ -1,5 +1,6 @@
import { Link } from 'preact-router/match' import { Link } from 'preact-router/match'
import { isAdministrator, User, UserRole } from '../../shared/model' import { isAdministrator, User, UserRole } from '../../shared/model'
import { prependBaseUrl } from '../api'
const ROLE_LABEL: Record<UserRole, string> = { const ROLE_LABEL: Record<UserRole, string> = {
['admin']: 'Admin', ['admin']: 'Admin',
@ -15,7 +16,7 @@ type Props = {
export const Header = ({ user, noLogin }: Props) => ( export const Header = ({ user, noLogin }: Props) => (
<header> <header>
<div class="logo"> <div class="logo">
<a href="/">PHC / Problemi</a> <a href={prependBaseUrl('/')}>PHC / Problemi</a>
</div> </div>
<nav> <nav>
{user ? ( {user ? (

@ -1,3 +1,4 @@
import { prependBaseUrl } from '../api'
import { Markdown } from './Markdown' import { Markdown } from './Markdown'
type Props = { type Props = {
@ -11,7 +12,7 @@ export const Problem = ({ id, content, solutionsCount }: Props) => {
<div class="problem"> <div class="problem">
<div class="problem-header"> <div class="problem-header">
<div class="problem-title"> <div class="problem-title">
<a href={`/problem/${id}`}>Problema {id}</a> <a href={prependBaseUrl(`/problem/${id}`)}>Problema {id}</a>
</div> </div>
</div> </div>
<div class="problem-content"> <div class="problem-content">

@ -8,7 +8,7 @@ import {
SolutionStatus, SolutionStatus,
UserId, UserId,
} from '../../shared/model' } from '../../shared/model'
import { server } from '../api' import { prependBaseUrl, server } from '../api'
import { Markdown } from './Markdown' import { Markdown } from './Markdown'
import { Select } from './Select' import { Select } from './Select'
@ -89,13 +89,14 @@ export const Solution = ({
{sentBy && ( {sentBy && (
<> <>
{' '} {' '}
di <a href={`/user/${sentBy}`}>@{sentBy}</a> di <a href={prependBaseUrl(`/user/${sentBy}`)}>@{sentBy}</a>
</> </>
)} )}
{forProblem && ( {forProblem && (
<> <>
{' '} {' '}
per il <a href={`/problem/${forProblem}`}>Problema {forProblem}</a> per il{' '}
<a href={prependBaseUrl(`/problem/${forProblem}`)}>Problema {forProblem}</a>
</> </>
)} )}
{!isNaN(d as any) && ( {!isNaN(d as any) && (

@ -1,7 +1,7 @@
import { StateUpdater, useEffect, useState } from 'preact/hooks' import { StateUpdater, useEffect, useState } from 'preact/hooks'
import { createContext } from 'preact' import { createContext } from 'preact'
import { server } from './api' import { prependBaseUrl, server } from './api'
import { User } from '../shared/model' import { User } from '../shared/model'
type Metadata = { type Metadata = {
@ -13,9 +13,7 @@ export const MetadataContext = createContext<Metadata>({})
export const ServerContext = createContext<boolean>(false) export const ServerContext = createContext<boolean>(false)
export const ClientContext = createContext<boolean>(false) export const ClientContext = createContext<boolean>(false)
type CurrentUserHook = ( type CurrentUserHook = (onLoaded?: (user: User | null) => void) => [User | null, () => Promise<void>]
onLoaded?: (user: User | null) => void
) => [User | null, () => Promise<void>]
export const useCurrentUser: CurrentUserHook = onLoaded => { export const useCurrentUser: CurrentUserHook = onLoaded => {
const [user, setUser] = useState(null) const [user, setUser] = useState(null)
@ -49,7 +47,8 @@ export const useResource: ResourceHookFunction = (url, initialValue) => {
function refresh() { function refresh() {
const controller = new AbortController() const controller = new AbortController()
const realUrl = typeof url === 'function' ? url() : url const realUrl = typeof url === 'function' ? url() : url
fetch(realUrl, { signal: controller.signal })
fetch(prependBaseUrl(realUrl), { signal: controller.signal })
.then(res => { .then(res => {
if (res.ok) { if (res.ok) {
return res.json() return res.json()

@ -7,7 +7,8 @@ export const LoginPage = () => {
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
const login = async () => { const login = async () => {
await fetch(`/api/login`, { // @ts-ignore
await fetch(`${import.meta.env.BASE_URL}/api/login`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -30,9 +31,7 @@ export const LoginPage = () => {
id="login-username" id="login-username"
type="text" type="text"
value={username} value={username}
onInput={e => onInput={e => setUsername(e.target instanceof HTMLInputElement ? e.target.value : '')}
setUsername(e.target instanceof HTMLInputElement ? e.target.value : '')
}
onKeyDown={e => e.key === 'Enter' && login()} onKeyDown={e => e.key === 'Enter' && login()}
/> />

@ -20,6 +20,7 @@
"body-parser": "^1.20.1", "body-parser": "^1.20.1",
"chalk": "^5.1.2", "chalk": "^5.1.2",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",
"katex": "^0.16.3", "katex": "^0.16.3",

@ -11,6 +11,7 @@ specifiers:
chalk: ^5.1.2 chalk: ^5.1.2
concurrently: ^7.5.0 concurrently: ^7.5.0
cookie-parser: ^1.4.6 cookie-parser: ^1.4.6
dotenv: ^16.0.3
esbuild: ^0.15.13 esbuild: ^0.15.13
express: ^4.18.2 express: ^4.18.2
http-status-codes: ^2.2.0 http-status-codes: ^2.2.0
@ -38,6 +39,7 @@ dependencies:
body-parser: 1.20.1 body-parser: 1.20.1
chalk: 5.1.2 chalk: 5.1.2
cookie-parser: 1.4.6 cookie-parser: 1.4.6
dotenv: 16.0.3
express: 4.18.2 express: 4.18.2
http-status-codes: 2.2.0 http-status-codes: 2.2.0
katex: 0.16.3 katex: 0.16.3
@ -935,6 +937,11 @@ packages:
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
dev: false dev: false
/dotenv/16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
dev: false
/ee-first/1.1.1: /ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false dev: false

@ -1,6 +1,8 @@
import path from 'path' import path from 'path'
import fs from 'fs/promises' import fs from 'fs/promises'
import dotenv from 'dotenv'
import express, { Handler, Router } from 'express' import express, { Handler, Router } from 'express'
import morgan from 'morgan' import morgan from 'morgan'
@ -9,6 +11,9 @@ import { createServer as createViteServer } from 'vite'
import { createApiRouter } from './server/routes' import { createApiRouter } from './server/routes'
import { RenderFunction } from './shared/ssr' import { RenderFunction } from './shared/ssr'
// Load ".env"
dotenv.config()
const HTML_ROUTES = ['/', '/login', '/problem/:id', '/admin', '/profile'] const HTML_ROUTES = ['/', '/login', '/problem/:id', '/admin', '/profile']
const config = { const config = {
@ -47,8 +52,7 @@ async function createDevRouter() {
const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml) const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml)
// Load (to be bundled) entry point for server side rendering // Load (to be bundled) entry point for server side rendering
const render: RenderFunction = (await vite.ssrLoadModule('./client/entry-server.tsx')) const render: RenderFunction = (await vite.ssrLoadModule('./client/entry-server.tsx')).default
.default
const { html, metadata } = render(req.originalUrl) const { html, metadata } = render(req.originalUrl)
@ -58,9 +62,7 @@ async function createDevRouter() {
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}` const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`
const metaTagsHtml = const metaTagsHtml =
'' + '' +
(metadata.title (metadata.title ? `<meta property="og:title" content="${metadata.title}" />\n` : '') +
? `<meta property="og:title" content="${metadata.title}" />\n`
: '') +
(metadata.description (metadata.description
? `<meta property="og:description" content="${metadata.description}" />\n` ? `<meta property="og:description" content="${metadata.description}" />\n`
: '') + : '') +
@ -91,10 +93,7 @@ async function createProductionRouter() {
r.use('/', express.static('dist/entry-client')) r.use('/', express.static('dist/entry-client'))
mountIndexHtmlRoutes(r, async (req, res) => { mountIndexHtmlRoutes(r, async (req, res) => {
const transformedTemplate = await fs.readFile( const transformedTemplate = await fs.readFile(path.resolve('./dist/entry-client/index.html'), 'utf-8')
path.resolve('./dist/entry-client/index.html'),
'utf-8'
)
const { html, metadata } = render(req.originalUrl) const { html, metadata } = render(req.originalUrl)

@ -53,7 +53,7 @@ export async function createApiRouter() {
}, },
} }
const db = createDatabase('./db.local.json', initialDatabaseValue) const db = createDatabase(process.env.DATABASE_PATH ?? './db.local.json', initialDatabaseValue)
async function getRequestUser(req: Request) { async function getRequestUser(req: Request) {
const userId = sessions.getUserForSession(req.cookies.sid) const userId = sessions.getUserForSession(req.cookies.sid)
@ -159,9 +159,7 @@ export async function createApiRouter() {
let solutions = await getSolutions(db) let solutions = await getSolutions(db)
// se l'utente non è loggato o se non è un amministratore allora mostra solo le soluzioni "visibili" // se l'utente non è loggato o se non è un amministratore allora mostra solo le soluzioni "visibili"
if (!requestUser || !isAdministrator(requestUser.role)) { if (!requestUser || !isAdministrator(requestUser.role)) {
solutions = solutions.filter( solutions = solutions.filter(s => s.visible || (requestUser && s.sentBy === requestUser.id))
s => s.visible || (requestUser && s.sentBy === requestUser.id)
)
} }
// filtra rispetto agli utenti // filtra rispetto agli utenti
if (queryUser !== null) { if (queryUser !== null) {
@ -232,10 +230,7 @@ export async function createApiRouter() {
return return
} }
// uno studente può modificare solo il campo "content" // uno studente può modificare solo il campo "content"
if ( if (user.role === 'student' && !validateObjectKeys<keyof SolutionModel>(req.body, ['content'])) {
user.role === 'student' &&
!validateObjectKeys<keyof SolutionModel>(req.body, ['content'])
) {
res.status(StatusCodes.UNAUTHORIZED) res.status(StatusCodes.UNAUTHORIZED)
res.send(`a student can only modify the field "content"`) res.send(`a student can only modify the field "content"`)
return return

@ -17,7 +17,8 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact" "jsxImportSource": "preact",
"types": ["vite/client"]
}, },
"include": ["client", "shared"], "include": ["client", "shared"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]

@ -1,7 +1,10 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import preactPlugin from '@preact/preset-vite' import preact from '@preact/preset-vite'
console.log(`[Config] BASE_URL = "${process.env.BASE_URL}"`)
export default defineConfig({ export default defineConfig({
plugins: [preactPlugin()], base: process.env.BASE_URL,
plugins: [preact()],
}) })

Loading…
Cancel
Save