85 Commits
Author SHA1 Message Date
aziis98 d358825da7 feat: initial prototype of admin pages 2024-11-27 22:10:29 +01:00
aziis98 75d2bb00ca fix: .project-list layout bugs
continuous-integration/drone/push Build is passing
2024-11-22 00:50:59 +01:00
aziis98 a8a327e5a4 fix: ios browser icon
continuous-integration/drone/push Build is passing
2024-11-22 00:30:11 +01:00
aziis98 61b43f8868 fix: firefox/chrome css parity
continuous-integration/drone/push Build is passing
2024-11-22 00:19:54 +01:00
aziis98 c5e0241354 FIX: added all page titles
continuous-integration/drone/push Build is passing
2024-11-22 00:13:02 +01:00
aziis98 a5d3edf6db chore: minor refactor into some components and updated favicon
continuous-integration/drone/push Build is passing
2024-11-22 00:06:23 +01:00
aziis98 1d797e2dbe fixed some TS and a wrong image import path
continuous-integration/drone/push Build is passing
2024-11-21 22:58:22 +01:00
aziis98 58e8e5d43c chore: better gallery layout and astro image tags
continuous-integration/drone/push Build is failing
2024-11-21 22:52:05 +01:00
aziis98 48bd9e459c feat: new PHC logo
continuous-integration/drone/push Build is passing
2024-11-21 16:00:38 +01:00
Luca Lombardo ae490db61f Non so scrivere il cognome di Antonio
continuous-integration/drone/push Build is passing
2024-11-21 07:15:33 +01:00
Luca Lombardo a6d5fdec57 Aggiunte due sezioni alla storia
continuous-integration/drone/push Build is passing
2024-11-21 07:14:42 +01:00
aziis98 5844dbb422 added an image to the gallery
continuous-integration/drone/push Build is passing
2024-11-19 14:42:48 +01:00
aziis98 a4a64e1d0b chore: better meta tags for link previews
continuous-integration/drone/push Build is passing
2024-11-19 00:59:41 +01:00
aziis98 10634b3293 feat: goatcounter for analytics
continuous-integration/drone/push Build is passing
2024-11-19 00:52:41 +01:00
Luca Lombardo 8a5727fcd6 Merge branch 'main' of git.phc.dm.unipi.it:phc/website
continuous-integration/drone/push Build is passing
2024-11-18 08:03:57 +01:00
Luca Lombardo dda3090069 Git 101, piccolo errore sistemato 2024-11-18 08:03:45 +01:00
aziis98 33d7e654c5 chore: fixing minor astro warnings
continuous-integration/drone/push Build is passing
2024-11-18 00:16:01 +01:00
aziis98 b6ad4ebf4d chore: more stable .drone.yml
continuous-integration/drone/push Build is passing
2024-11-18 00:01:22 +01:00
aziis98 0c6218f27f add: guides now show author and tags
continuous-integration/drone/push Build is passing
2024-11-17 23:55:23 +01:00
aziis98 9653a14cdc fix: images in storia
continuous-integration/drone/push Build is passing
2024-11-17 23:14:34 +01:00
aziis98 3bf7b77b0f Merge branch 'main' of git.phc.dm.unipi.it:phc/website 2024-11-17 22:50:54 +01:00
aziis98 f90493ea34 fix: guide links and firefox/chrome styles 2024-11-17 22:50:46 +01:00
Luca Lombardo 7c9533cf62 Foto per la storia (unstable su mobile?)
continuous-integration/drone/push Build is passing
2024-11-17 22:33:22 +01:00
Luca Lombardo 7e257ea7c3 Aggiunto 2024 alla timeline
continuous-integration/drone/push Build is passing
2024-11-17 22:26:56 +01:00
aziis98 c868cd4706 Merge branch 'main' of git.phc.dm.unipi.it:phc/website 2024-11-17 22:09:52 +01:00
Luca Lombardo f7d3d394d0 memino
continuous-integration/drone/push Build is passing
2024-11-17 22:09:26 +01:00
aziis98 07f947edb6 Merge branch 'main' of git.phc.dm.unipi.it:phc/website 2024-11-17 22:07:30 +01:00
aziis98 0dee1e8bff fix: use id instead of slug for guides 2024-11-17 22:07:25 +01:00
Luca Lombardo cd15b3733b Guida su come recuperare la password Poisson
continuous-integration/drone/push Build is passing
2024-11-17 22:06:19 +01:00
aziis98 c44b370652 Merge branch 'main' of git.phc.dm.unipi.it:phc/website 2024-11-17 21:55:32 +01:00
aziis98 529400fde4 fix: added favicon 2024-11-17 21:55:19 +01:00
Luca Lombardo eee08e274d Prima bozza di Git 101
continuous-integration/drone/push Build is passing
2024-11-17 21:54:02 +01:00
Luca Lombardo 31b14252cb Guida su come attivare account poisson
continuous-integration/drone/push Build is passing
2024-11-17 21:26:05 +01:00
BachoSeven 1df06a8c88 Add drone file, cleanup Astro config
continuous-integration/drone/push Build is passing
2024-11-17 18:52:52 +01:00
BachoSeven 46a3a76660 Remove dribble 2024-11-17 18:27:53 +01:00
BachoSeven af460ae9dc fix all the things before deploying 2024-11-17 18:15:30 +01:00
aziis98 5d57dd7a9a some tweaks 2024-11-03 22:22:54 +01:00
aziis98 d9b4834268 aggiunta una guida su come deploy-are con le github actions su poisson in modo sicuro 2024-11-03 22:08:14 +01:00
aziis98 556d363bc9 updated readme 2024-11-02 00:27:16 +01:00
aziis98 c77123d062 chore: reordered images 2024-11-02 00:23:51 +01:00
aziis98 3600ae60d4 fix: removed scss warning about nested props 2024-11-02 00:23:38 +01:00
aziis98 d8c7b0f562 some changes 2024-10-23 22:06:18 +02:00
aziis98 da72cd3411 chore: more stylistic changes 2024-10-18 14:56:21 +02:00
aziis98 66c8869671 fix: some wrong widths 2024-10-18 14:11:37 +02:00
aziis98 dcbb069ef1 feat: finalize end section of the homepage 2024-10-18 00:55:13 +02:00
aziis98 d8208742f2 style: Fixed mobile, enhanced homepage 2024-10-18 00:09:34 +02:00
aziis98 68afc97c07 feat: more mobile friendly pages 2024-10-12 03:50:59 +02:00
aziis98 2cbef56377 feat: header and utenti page mobile friendly 2024-10-12 03:32:28 +02:00
aziis98 f287071674 chore: some changes 2024-10-11 17:36:39 +02:00
aziis98 c8771142d5 fix 2024-10-10 15:13:06 +02:00
aziis98 890fbc14c3 chore: removed icons CDN and other tweaks 2024-10-10 14:46:43 +02:00
BachoSeven 0d2dd05f50 ok tutorato (by radiotoe) 2024-10-10 00:47:33 +02:00
BachoSeven 9a10fe1ff0 fiat combobox + happy utenti page uWu 2024-10-10 00:37:08 +02:00
aziis98 de28d3ad6e utenti: filter by user category 2024-10-06 18:50:17 +02:00
aziis98 7398f27035 feat: users page with non working filter button 2024-10-06 18:14:05 +02:00
aziis98 87d65b52b5 feat: poisson user page with fuzzy search 2024-10-06 17:46:23 +02:00
aziis98 2b09c6c29c chore: vscode recommended settings 2024-10-06 15:57:05 +02:00
aziis98 3e041421e5 chore: reversed list and added bun.lock file 2024-10-06 02:54:17 +02:00
aziis98 69a0abc1f2 merge: coding session with fra 2024-10-06 02:47:15 +02:00
aziis98 7e99cfa9d7 feat: utenti page and poisson integration 2024-10-06 02:45:49 +02:00
BachoSeven d06e731edd wow such rich history 2024-10-06 02:43:42 +02:00
aziis98 a87c87eb10 chore: minor stylistic changes 2024-08-03 03:31:31 +02:00
aziis98 3316f6a6ec fix: wrong props type 2024-08-03 03:21:40 +02:00
aziis98 27317b38b1 refactor: renamed "pageName" to "pageTags" 2024-08-03 03:20:26 +02:00
aziis98 1509984afb reworked homepage, prototype storia timeline 2024-08-03 03:15:50 +02:00
aziis98 fb43eddefc working on css design system 2024-08-02 19:07:18 +02:00
aziis98 3885b5135b starting work on abstracting card design 2024-08-02 00:01:11 +02:00
aziis98 3428bda4bc started work on design page 2024-08-01 19:44:38 +02:00
BachoSeven 502ef6ea73 changed font 2024-07-30 19:01:38 +02:00
aziis98 eb857b6dba iniziato un post su astro 2024-07-30 18:28:07 +02:00
aziis98 ce42b2bf15 miglioramenti stilistici 2024-07-30 15:41:20 +02:00
BachoSeven 8f1d6cfff1 add notizie, rework homepage and fill with content 2024-07-28 19:51:13 +02:00
BachoSeven 13b0c03e5f Add tags to guide/ 2024-03-17 16:51:12 +01:00
Antonio De Lucreziis f16e340fd2 chore: some changes and run prettier everywhere 2024-03-14 21:25:00 +01:00
aziis98 182238f89d feat: appunti and guide prototype page styling 2024-03-12 23:04:49 +01:00
aziis98 16bffed2be chore: styling 2024-03-11 19:41:44 +01:00
BachoSeven b4722ac030 feat: add login page 2024-03-11 17:28:53 +01:00
aziis98 cf9ac2994a chore: some changes 2024-03-07 18:45:26 +01:00
aziis98 16c8a6dd7a chore: added some scss mixins 2024-03-02 02:08:52 +01:00
aziis98 d34d169d17 add: prototipo pagina utenti con barra di ricerca 2024-02-29 17:13:59 +01:00
aziis98 6c15c6ac7b chore: updated readme 2024-02-29 13:01:49 +01:00
aziis98 eca5d60fd6 add: some styles and more organization 2024-02-29 12:54:28 +01:00
aziis98 70ccf24c6b add: folder structure for markdown content pages 2024-02-29 11:08:31 +01:00
BachoSeven a91ad8398d add prettier, fix TS stuff 2024-02-29 00:37:10 +01:00
BachoSeven ff3a44e23c Initial commit of Astro project: drizzle database 2024-02-29 00:24:10 +01:00
111 changed files with 5789 additions and 2802 deletions
+50
View File
@@ -0,0 +1,50 @@
# This file defines a Drone pipeline that builds a static website with "npm run build". This
# pipeline must be marked as "Trusted" in the Drone project settings.
#
# We mount the target directory of the project at "/var/www/{project}" to the container
# "dist/" directory and the run the build. A caveat is that the container builds files
# with "root" permissions, so we need to fix those after each build with a second pipeline.
kind: pipeline
name: default
steps:
- name: deploy
image: node:latest
volumes:
- name: host-website-dist
path: /mnt/website
commands:
- npm install
- npm run build
- cp -rT ./dist /mnt/website
volumes:
- name: host-website-dist
host: # this volume is mounted on the host machine
path: /var/www/website
trigger:
branch:
- main
event:
- push
---
kind: pipeline
type: exec # this job is executed on the host machine
name: caddy-permissions
depends_on:
- default
steps:
- name: chown
commands:
- chown -R caddy:caddy /var/www/website
trigger:
branch:
- main
event:
- push
+20 -7
View File
@@ -1,10 +1,23 @@
.env
*.local*
bin/
.out/
out/
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
.vscode/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# local data
*.local*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
-2
View File
@@ -1,2 +0,0 @@
# Needed by pnpm to work with "@preact/preset-vite"
shamefully-hoist=true
+9
View File
@@ -0,0 +1,9 @@
{
"printWidth": 110,
"singleQuote": true,
"quoteProps": "consistent",
"tabWidth": 4,
"useTabs": false,
"semi": false,
"arrowParens": "avoid"
}
+3
View File
@@ -0,0 +1,3 @@
{
"npm.packageManager": "bun"
}
-31
View File
@@ -1,31 +0,0 @@
# Architettura
Questo è un progetto _fullstack_ con una backend scritta in [Go](https://go.dev/) ed una frontend in JS, più precisamente utilizziamo NodeJS con [Vite](https://vitejs.dev/) per build-are la frontend.
La cosa più interessante è l'integrazione tra **Vite** e la backend in **Go** per siti con più pagine (il nostro caso). Di base Vite supporta siti con più file html ma ha bisogno che gli venga detto quali sono tutti gli _entrypoint_ HTML. Vedremo che questo progetto usa una tecnica che ci permette di indicare una volta sola le cose nel codice in Go senza stare a tenere sincronizzato il codice in Go e la configurazione di Vite.
## Usage
Ci sono vari modi per lanciare la nostra applicazione in base all'_environment_, in particolare sono tutti isolati sotto forma di eseguibili in Go:
- Quando saremo in produzione l'unico server sarà quello di Go lanciato attraverso l'_entry-point_ [./cmd/server/main.go](./cmd/server/main.go)
In particolare prima di poter lanciare questo server bisogna aver eseguito [./cmd/build/main.go](./cmd/build/main.go) che esegue solo il codice relativo al router della nostra applicazione e genera un file `out/routes.json`. Poi bisogna eseguire `npm run build` che chiama Vite e genera il codice per tutte le route dentro la cartella `out/frontend/`.
- Quando siamo in development usiamo solo [./cmd/devserver/main.go](./cmd/devserver/main.go) che lancia in background il server di Vite (chiama `npm run dev` che a sua volta è un alias per `node server.js`) quindi possiamo vedere tutto in tempo reale da `localhost:3000`.
Più precisamente il server di Vite all'avvio richiede al server in Go tutte le route da montare utilizzando la route speciale `/api/development/routes` (in particolare Fiber ed ExpressJS hanno la stessa sintassi per definire le route quindi questa cosa è facile da fare).
Poi quando si prova ad andare su una pagina ci sono due casi
- Se la route era **statica** allora leggiamo il file html, lo facciamo processare a Vite e poi lo rimandiamo all'utente.
- Se invece la route era di tipo **dinamico** allora leggiamo sempre il file e lo processiamo con Vite però ora utilizziamo l'altra route speciale che esiste solo in fase di sviluppo `/api/development/render` che renderizza la pagina applicando il _templating del server_ e poi una volta finito inviamo la pagina al client.
Invece quando saremo in produzione tutte le pagina saranno già state renderizzate da Vite quindi saremo nel caso standard di _http server_ con views da renderizzare con il _template engine_ del caso prima di mandare la pagina al client.
- L'ultimo _entrypoint_ è [./cmd/build/main.go](./cmd/build/main.go) e lancia la nostra applicazione in una modalità "finta" senza server http ma vengono comunque registrate tutte le route utilizzando sempre il modulo `dev`. Questo ci permette di costruire l'albero delle route (statiche e dinamiche) che poi servirà a Vite quando faremo `npm run build`.
Ciò serve perché così ci basta definire tutte le route una volta sola nel Go e poi in automatico funzioneranno anche nel server di Vite senza dover ripetere due volte il codice. (questa è la parte più magica di _meta-programming_ di tutto il progetto)
![architecture-1](./docs/architecture-1.svg)
-10
View File
@@ -1,10 +0,0 @@
.PHONY: test
test:
PROJECT_DIR="$(shell pwd)" go test -v ./...
.PHONY: build
build:
go run -v ./cmd/build
pnpm run build
go build -o ./out/bin/server ./cmd/server
+35 -18
View File
@@ -1,25 +1,42 @@
# Website 2
# PHC Website
Repo per il nuovo sito del PHC
Questo è il repository del sito web del PHC. Il sito è costruito utilizzando Astro, un framework statico per la generazione di siti web.
## Docs
## Installazione
- [./ARCHITECTURE.md](./ARCHITECTURE.md)
Alcune note sull'architettura di questo progetto.
## Usage
## Development
```bash shell
# Starts the backend on port :4000 and the frontend development server on port :3000
$ go run -v ./cmd/devserver
```bash
bun install
```
## Production
## Sviluppo
```bash shell
# Generates "routes.json", builds all frontend artifacts and finally the main binary
$ make build
```bash
bun dev
```
## Build
```bash
bun build
```
## Deploy [TODO]
Il progetto contiene un `Dockerfile` che viene usato per il deploy (del server prodotto da Astro).
```bash
docker build -t phc-website .
docker run -p 3000:3000 phc-website
```
C'è anche un `.drone.yml` che viene usato per il deploy su un server remoto utilizzando Drone per il CD.
## Come Contribuire
**Nota per Sviluppatori**: SE il branch `dev` non esiste o è indietro rispetto a `main`, bisogna portarlo avanti a `main` e poi continuare con le modifiche dal lì.
Sentitevi liberi di aprire una PR per qualsiasi modifica o aggiunta al sito web. Il branch `main` è protetto e corrisponde alla versione di produzione del sito web, le modifiche devono prima essere accettate su `dev` e poi mergeate su `main` da un amministratore.
### Cose da fare
- Aggiungere guide in [src/content/guides/](./src/content/guides/)
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from 'astro/config'
import preact from '@astrojs/preact'
import mdx from '@astrojs/mdx'
// https://astro.build/config
export default defineConfig({
server: {
port: 3000,
},
markdown: {
shikiConfig: {
theme: 'github-light',
},
},
integrations: [preact(), mdx()],
output: 'static'
})
Executable
BIN
View File
Binary file not shown.
-44
View File
@@ -1,44 +0,0 @@
package main
import (
"encoding/json"
"log"
"os"
"git.phc.dm.unipi.it/phc/website/services/config"
"git.phc.dm.unipi.it/phc/website/services/database"
"git.phc.dm.unipi.it/phc/website/services/server"
"git.phc.dm.unipi.it/phc/website/services/server/dev"
"git.phc.dm.unipi.it/phc/website/sl"
)
func main() {
l := sl.New()
// sl.Inject[config.Interface](l, &config.EnvConfig{})
sl.InjectValue(l, config.Slot, &config.Config{
Mode: "production",
})
sl.InjectValue(l, database.Slot, database.Database(
&database.Memory{}),
)
if _, err := server.Configure(l); err != nil {
log.Fatal(err)
}
f, err := os.Create("out/routes.json")
if err != nil {
log.Fatal(err)
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(dev.UseRoutesMetadata(l)); err != nil {
log.Fatal(err)
}
log.Printf(`generated "out/routes.json"`)
}
-69
View File
@@ -1,69 +0,0 @@
package main
import (
"bufio"
"io"
"log"
"os/exec"
"git.phc.dm.unipi.it/phc/website/model"
"git.phc.dm.unipi.it/phc/website/services/config"
"git.phc.dm.unipi.it/phc/website/services/database"
"git.phc.dm.unipi.it/phc/website/services/server"
"git.phc.dm.unipi.it/phc/website/sl"
)
func init() {
log.SetFlags(0)
}
func main() {
l := sl.New()
cfg := sl.InjectValue(l, config.Slot, &config.Config{
Mode: "development",
Host: ":4000",
})
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{
{
Id: "claire",
FullName: "Claire Doe",
Nickname: "claire-doe",
AuthSources: map[string]model.AuthSource{},
},
{
Id: "john",
FullName: "John Smith",
Nickname: "john-smith",
AuthSources: map[string]model.AuthSource{},
},
},
})
srv, err := server.Configure(l)
if err != nil {
log.Fatal(err)
}
go func() {
log.Fatal(srv.Router.Listen(cfg.Host))
}()
r, w := io.Pipe()
cmd := exec.Command("npm", "run", "dev")
cmd.Stdout = w
go func() {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
log.Printf(`[cmd/devserver] [vitejs] %s`, scanner.Text())
}
}()
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
-44
View File
@@ -1,44 +0,0 @@
package main
import (
"log"
"git.phc.dm.unipi.it/phc/website/model"
"git.phc.dm.unipi.it/phc/website/services/config"
"git.phc.dm.unipi.it/phc/website/services/database"
"git.phc.dm.unipi.it/phc/website/services/server"
"git.phc.dm.unipi.it/phc/website/sl"
)
func main() {
l := sl.New()
cfg := sl.InjectValue(l, config.Slot, &config.Config{
Mode: "production",
Host: ":4000",
})
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{
{
Id: "claire",
FullName: "Claire Doe",
Nickname: "claire-doe",
AuthSources: map[string]model.AuthSource{},
},
{
Id: "john",
FullName: "John Smith",
Nickname: "john-smith",
AuthSources: map[string]model.AuthSource{},
},
},
})
srv, err := server.Configure(l)
if err != nil {
log.Fatal(err)
}
log.Fatal(srv.Router.Listen(cfg.Host))
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 251 KiB

-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Homepage</title>
</head>
<body>
Homepage
</body>
</html>
-17
View File
@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ .Title }} &bull; Articoli &bull; PHC</title>
<link rel="stylesheet" href="/styles/main.scss" />
<link rel="stylesheet" href="./typography.scss" />
</head>
<body>
<h1>Articolo "{{ .Example }}"</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque, quasi...</p>
</body>
</html>
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Articoli &bull; PHC</title>
<link rel="stylesheet" href="/styles/main.scss" />
</head>
<body>
<h1>Articoli</h1>
{{ .Example }}
</body>
</html>
-3
View File
@@ -1,3 +0,0 @@
h1 {
color: red;
}
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ListaUtenti</title>
</head>
<body>
<main class="page-lista-utenti"></main>
<script type="module" src="./src/main.jsx"></script>
</body>
</html>
@@ -1,3 +0,0 @@
.list {
background: red;
}
-13
View File
@@ -1,13 +0,0 @@
import { render } from 'preact'
import './lista-utenti.scss'
render(
<>
<h1>Lista Utenti</h1>
<div class="list">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Odit, illo molestiae. Sapiente cumque saepe maxime, temporibus ad
nulla id officiis impedit ut dolorem asperiores voluptate illo, molestiae facilis inventore. Ea.
</div>
</>,
document.querySelector('main')
)
-5
View File
@@ -1,5 +0,0 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
-22
View File
@@ -1,22 +0,0 @@
module git.phc.dm.unipi.it/phc/website
go 1.19
require (
github.com/alecthomas/repr v0.2.0
github.com/gofiber/fiber/v2 v2.41.0
github.com/joho/godotenv v1.4.0
github.com/valyala/fasthttp v1.43.0
)
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.1.0 // indirect
)
-40
View File
@@ -1,40 +0,0 @@
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/gofiber/fiber/v2 v2.41.0 h1:YhNoUS/OTjEz+/WLYuQ01xI7RXgKEFnGBKMagAu5f0M=
github.com/gofiber/fiber/v2 v2.41.0/go.mod h1:RdebcCuCRFp4W6hr3968/XxwJVg0K+jr9/Ae0PFzZ0Q=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g=
github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-18
View File
@@ -1,18 +0,0 @@
import fetch from 'node-fetch'
import { readFile } from 'fs/promises'
export async function getBuildRoutesMetadata(file) {
console.log('Loading routes from disk...')
const routesRaw = await readFile(file, 'utf8')
return JSON.parse(routesRaw)
}
export async function getDevRoutesMetadata(url) {
console.log('Loading routes from go server...')
const routesReq = await fetch(url)
const routes = await routesReq.json()
return routes
}
-15
View File
@@ -1,15 +0,0 @@
package model
type User struct {
Id string
FullName string
Nickname string
AuthSources map[string]AuthSource
}
type AuthSource struct {
Provider string
AuthToken string
}
+36 -13
View File
@@ -1,21 +1,44 @@
{
"name": "website",
"version": "1.0.0",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "node server.js",
"build": "vite build --emptyOutDir"
},
"devDependencies": {
"@preact/preset-vite": "^2.5.0",
"axios": "^1.2.6",
"express": "^4.18.2",
"morgan": "^1.10.0",
"node-fetch": "^3.3.0",
"sass": "^1.57.1",
"vite": "^4.0.4"
"dev": "run-s astro:sync astro:dev",
"build": "run-s astro:build",
"astro:sync": "astro sync",
"astro:dev": "astro dev",
"astro:build": "astro check && astro build"
},
"dependencies": {
"preact": "^10.11.3"
"@astrojs/check": "^0.9.4",
"@astrojs/node": "^8.3.4",
"@astrojs/preact": "^3.5.3",
"@fontsource-variable/material-symbols-outlined": "^5.1.1",
"@fontsource/iosevka": "^5.0.11",
"@fontsource/mononoki": "^5.0.11",
"@fontsource/open-sans": "^5.0.24",
"@fontsource/source-code-pro": "^5.0.16",
"@fontsource/source-sans-pro": "^5.0.8",
"@fontsource/space-mono": "^5.0.20",
"@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7",
"astro": "^4.15.11",
"fuse.js": "^7.0.0",
"katex": "^0.16.9",
"preact": "^10.19.6",
"typescript": "^5.3.3"
},
"devDependencies": {
"@astrojs/mdx": "^3.1.7",
"@types/katex": "^0.16.7",
"jsdom": "^24.1.1",
"linkedom": "^0.18.4",
"npm-run-all": "^4.1.5",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"sass": "^1.71.1",
"tsx": "^4.7.1"
}
}
-1495
View File
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
<svg width="1000" height="500" viewBox="0 0 1000 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="80" y="190" width="60" height="120" fill="#1E6733" />
<rect x="160" y="50" width="150" height="60" fill="#1E6733" />
<rect x="140" y="90" width="10" height="50" fill="#ECC333" />
<rect x="140" y="200" width="10" height="50" fill="#ECC333" />
<rect x="140" y="410" width="10" height="20" fill="#ECC333" />
<rect x="140" y="350" width="10" height="50" fill="#ECC333" />
<rect x="240" y="110" width="70" height="10" fill="#ECC333" />
<rect x="250" y="130" width="60" height="130" fill="#1E6733" />
<rect x="340" y="50" width="60" height="120" fill="#1E6733" />
<rect x="340" y="190" width="60" height="120" fill="#1E6733" />
<rect x="590" y="190" width="60" height="120" fill="#1E6733" />
<rect x="690" y="180" width="60" height="120" fill="#1E6733" />
<rect x="690" y="310" width="60" height="140" fill="#1E6733" />
<rect x="690" y="50" width="60" height="120" fill="#1E6733" />
<rect x="590" y="320" width="60" height="130" fill="#1E6733" />
<rect x="590" y="50" width="60" height="130" fill="#1E6733" />
<rect x="420" y="240" width="150" height="60" fill="#1E6733" />
<rect x="340" y="320" width="60" height="130" fill="#1E6733" />
<rect x="240" y="140" width="10" height="50" fill="#ECC333" />
<rect x="350" y="170" width="40" height="10" fill="#ECC333" />
<rect x="330" y="330" width="10" height="50" fill="#ECC333" />
<rect x="160" y="200" width="80" height="60" fill="#1E6733" />
<rect x="650" y="200" width="10" height="50" fill="#ECC333" />
<rect x="750" y="330" width="10" height="60" fill="#ECC333" />
<rect x="800" y="450" width="40" height="10" fill="#ECC333" />
<rect x="850" y="450" width="30" height="10" fill="#ECC333" />
<rect x="750" y="90" width="10" height="50" fill="#ECC333" />
<rect x="810" y="110" width="60" height="10" fill="#ECC333" />
<rect x="580" y="330" width="10" height="50" fill="#ECC333" />
<rect x="580" y="60" width="10" height="50" fill="#ECC333" />
<rect x="710" y="420" width="20" height="20" fill="#C4C4C4" />
<rect x="710" y="390" width="20" height="20" fill="#C4C4C4" />
<rect x="100" y="280" width="20" height="10" fill="#C4C4C4" />
<rect x="100" y="260" width="20" height="10" fill="#C4C4C4" />
<rect x="110" y="210" width="20" height="20" fill="#C4C4C4" />
<rect x="350" y="430" width="40" height="10" fill="#303030" />
<rect x="350" y="410" width="40" height="10" fill="#303030" />
<rect x="350" y="390" width="40" height="10" fill="#303030" />
<rect x="700" y="70" width="20" height="40" fill="#303030" />
<rect x="700" y="120" width="20" height="40" fill="#303030" />
<rect x="610" y="280" width="20" height="20" fill="#C4C4C4" />
<rect x="600" y="240" width="20" height="20" fill="#C4C4C4" />
<rect x="430" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="430" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="430" y="280" width="10" height="10" fill="#C4C4C4" />
<rect x="445" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="445" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="460" y="280" width="40" height="10" fill="#C4C4C4" />
<rect x="475" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="475" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="505" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="505" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="520" y="265" width="10" height="15" fill="#C4C4C4" />
<rect x="505" y="280" width="25" height="10" fill="#C4C4C4" />
<rect x="535" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="535" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="535" y="280" width="10" height="10" fill="#C4C4C4" />
<rect x="445" y="280" width="10" height="10" fill="#C4C4C4" />
<rect x="460" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="460" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="490" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="490" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="520" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="550" y="250" width="10" height="10" fill="#C4C4C4" />
<rect x="550" y="265" width="10" height="10" fill="#C4C4C4" />
<rect x="550" y="280" width="10" height="10" fill="#C4C4C4" />
<rect x="620" y="210" width="20" height="20" fill="#C4C4C4" />
<rect x="370" y="70" width="20" height="30" fill="#303030" />
<rect x="370" y="110" width="20" height="30" fill="#303030" />
<rect x="870" y="440" width="40" height="10" transform="rotate(-90 870 440)" fill="#303030" />
<rect x="890" y="440" width="40" height="10" transform="rotate(-90 890 440)" fill="#303030" />
<rect x="810" y="440" width="40" height="10" transform="rotate(-90 810 440)" fill="#303030" />
<rect x="790" y="440" width="40" height="10" transform="rotate(-90 790 440)" fill="#303030" />
<rect x="270" y="100" width="40" height="10" transform="rotate(-90 270 100)" fill="#303030" />
<rect x="290" y="100" width="40" height="10" transform="rotate(-90 290 100)" fill="#303030" />
<rect x="190" y="100" width="40" height="10" transform="rotate(-90 190 100)" fill="#303030" />
<rect x="170" y="100" width="40" height="10" transform="rotate(-90 170 100)" fill="#303030" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M140 60V170C134.477 170 130 174.477 130 180H90C90 174.477 85.5228 170 80 170V60C85.5228 60 90 55.5228 90 50H130C130 55.5228 134.477 60 140 60Z"
fill="#1E6733" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M130 320H90C90 325.523 85.5229 330 80 330V440C85.5229 440 90 444.477 90 450H130C130 444.477 134.477 440 140 440V330C134.477 330 130 325.523 130 320Z"
fill="#1E6733" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M770 60C775.523 60 780 55.5228 780 50H910C910 55.5228 914.477 60 920 60V100C914.477 100 910 104.477 910 110H780C780 104.477 775.523 100 770 100V60Z"
fill="#1E6733" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M770 400C775.523 400 780 395.523 780 390H910C910 395.523 914.477 400 920 400V440C914.477 440 910 444.477 910 450H780C780 444.477 775.523 440 770 440V400Z"
fill="#1E6733" />
<rect x="750" y="190" width="10" height="40" fill="#ECC333" />
<rect x="750" y="240" width="10" height="20" fill="#ECC333" />
<rect x="400" y="200" width="10" height="40" fill="#ECC333" />
<rect x="400" y="250" width="10" height="20" fill="#ECC333" />
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

+87
View File
@@ -0,0 +1,87 @@
<svg width="1000" height="500" viewBox="0 0 1000 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="80" y="190" width="60" height="120" fill="#1E6733"/>
<rect x="160" y="50" width="150" height="60" fill="#1E6733"/>
<rect x="140" y="90" width="10" height="50" fill="#ECC333"/>
<rect x="140" y="200" width="10" height="50" fill="#ECC333"/>
<rect x="140" y="410" width="10" height="20" fill="#ECC333"/>
<rect x="140" y="350" width="10" height="50" fill="#ECC333"/>
<rect x="240" y="110" width="70" height="10" fill="#ECC333"/>
<rect x="250" y="130" width="60" height="130" fill="#1E6733"/>
<rect x="340" y="50" width="60" height="120" fill="#1E6733"/>
<rect x="340" y="190" width="60" height="120" fill="#1E6733"/>
<rect x="590" y="190" width="60" height="120" fill="#1E6733"/>
<rect x="690" y="180" width="60" height="120" fill="#1E6733"/>
<rect x="690" y="310" width="60" height="140" fill="#1E6733"/>
<rect x="690" y="50" width="60" height="120" fill="#1E6733"/>
<rect x="590" y="320" width="60" height="130" fill="#1E6733"/>
<rect x="590" y="50" width="60" height="130" fill="#1E6733"/>
<rect x="420" y="240" width="150" height="60" fill="#1E6733"/>
<rect x="340" y="320" width="60" height="130" fill="#1E6733"/>
<rect x="240" y="140" width="10" height="50" fill="#ECC333"/>
<rect x="350" y="170" width="40" height="10" fill="#ECC333"/>
<rect x="330" y="330" width="10" height="50" fill="#ECC333"/>
<rect x="160" y="200" width="80" height="60" fill="#1E6733"/>
<rect x="650" y="200" width="10" height="50" fill="#ECC333"/>
<rect x="750" y="330" width="10" height="60" fill="#ECC333"/>
<rect x="800" y="450" width="40" height="10" fill="#ECC333"/>
<rect x="850" y="450" width="30" height="10" fill="#ECC333"/>
<rect x="750" y="90" width="10" height="50" fill="#ECC333"/>
<rect x="810" y="110" width="60" height="10" fill="#ECC333"/>
<rect x="580" y="330" width="10" height="50" fill="#ECC333"/>
<rect x="580" y="60" width="10" height="50" fill="#ECC333"/>
<rect x="710" y="420" width="20" height="20" fill="#C4C4C4"/>
<rect x="710" y="390" width="20" height="20" fill="#C4C4C4"/>
<rect x="100" y="280" width="20" height="10" fill="#C4C4C4"/>
<rect x="100" y="260" width="20" height="10" fill="#C4C4C4"/>
<rect x="110" y="210" width="20" height="20" fill="#C4C4C4"/>
<rect x="350" y="430" width="40" height="10" fill="#303030"/>
<rect x="350" y="410" width="40" height="10" fill="#303030"/>
<rect x="350" y="390" width="40" height="10" fill="#303030"/>
<rect x="700" y="70" width="20" height="40" fill="#303030"/>
<rect x="700" y="120" width="20" height="40" fill="#303030"/>
<rect x="610" y="280" width="20" height="20" fill="#C4C4C4"/>
<rect x="600" y="240" width="20" height="20" fill="#C4C4C4"/>
<rect x="430" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="430" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="430" y="280" width="10" height="10" fill="#C4C4C4"/>
<rect x="445" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="445" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="460" y="280" width="40" height="10" fill="#C4C4C4"/>
<rect x="475" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="475" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="505" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="505" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="520" y="265" width="10" height="15" fill="#C4C4C4"/>
<rect x="505" y="280" width="25" height="10" fill="#C4C4C4"/>
<rect x="535" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="535" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="535" y="280" width="10" height="10" fill="#C4C4C4"/>
<rect x="445" y="280" width="10" height="10" fill="#C4C4C4"/>
<rect x="460" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="460" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="490" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="490" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="520" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="550" y="250" width="10" height="10" fill="#C4C4C4"/>
<rect x="550" y="265" width="10" height="10" fill="#C4C4C4"/>
<rect x="550" y="280" width="10" height="10" fill="#C4C4C4"/>
<rect x="620" y="210" width="20" height="20" fill="#C4C4C4"/>
<rect x="370" y="70" width="20" height="30" fill="#303030"/>
<rect x="370" y="110" width="20" height="30" fill="#303030"/>
<rect x="870" y="440" width="40" height="10" transform="rotate(-90 870 440)" fill="#303030"/>
<rect x="890" y="440" width="40" height="10" transform="rotate(-90 890 440)" fill="#303030"/>
<rect x="810" y="440" width="40" height="10" transform="rotate(-90 810 440)" fill="#303030"/>
<rect x="790" y="440" width="40" height="10" transform="rotate(-90 790 440)" fill="#303030"/>
<rect x="270" y="100" width="40" height="10" transform="rotate(-90 270 100)" fill="#303030"/>
<rect x="290" y="100" width="40" height="10" transform="rotate(-90 290 100)" fill="#303030"/>
<rect x="190" y="100" width="40" height="10" transform="rotate(-90 190 100)" fill="#303030"/>
<rect x="170" y="100" width="40" height="10" transform="rotate(-90 170 100)" fill="#303030"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M140 60V170C134.477 170 130 174.477 130 180H90C90 174.477 85.5228 170 80 170V60C85.5228 60 90 55.5228 90 50H130C130 55.5228 134.477 60 140 60Z" fill="#1E6733"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M130 320H90C90 325.523 85.5229 330 80 330V440C85.5229 440 90 444.477 90 450H130C130 444.477 134.477 440 140 440V330C134.477 330 130 325.523 130 320Z" fill="#1E6733"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M770 60C775.523 60 780 55.5228 780 50H910C910 55.5228 914.477 60 920 60V100C914.477 100 910 104.477 910 110H780C780 104.477 775.523 100 770 100V60Z" fill="#1E6733"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M770 400C775.523 400 780 395.523 780 390H910C910 395.523 914.477 400 920 400V440C914.477 440 910 444.477 910 450H780C780 444.477 775.523 440 770 440V400Z" fill="#1E6733"/>
<rect x="750" y="190" width="10" height="40" fill="#ECC333"/>
<rect x="750" y="240" width="10" height="20" fill="#ECC333"/>
<rect x="400" y="200" width="10" height="40" fill="#ECC333"/>
<rect x="400" y="250" width="10" height="20" fill="#ECC333"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="100%" height="2rem" viewBox="0 0 1 1" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="zig-zag" x="0" y="0" width="2" height="1">
<path fill="#C2A8EB" d="M 0,0 L 1,1 L 2,0 L 0,0"/>
</pattern>
</defs>
<rect fill="url(#zig-zag)" x="0" y="0" width="100%" height="1" />
</svg>

After

Width:  |  Height:  |  Size: 344 B

-93
View File
@@ -1,93 +0,0 @@
import express from 'express'
import { createServer as createViteServer } from 'vite'
import { getDevRoutesMetadata } from './meta/routes.js'
import fetch from 'node-fetch'
import { readFile } from 'fs/promises'
import { dirname, resolve } from 'path'
import morgan from 'morgan'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
async function main() {
const routes = await getDevRoutesMetadata('http://127.0.0.1:4000/api/development/routes')
console.log('Found static routes:')
for (const [route, file] of Object.entries(routes.static)) {
console.log(`- ${route} -> "${file}"`)
}
console.log('Found dynamic routes:')
for (const [route, file] of Object.entries(routes.dynamic)) {
console.log(`- ${route} -> "${file}"`)
}
const app = express()
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'))
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom',
})
app.use(vite.middlewares)
for (const [route, file] of Object.entries(routes.static)) {
app.get(route, async (req, res) => {
console.log(`Requested static route "${route}":`)
let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
// Replace "./" with the absolute path of the html page
htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
console.log(`- applying vite transformations for "${file}"`)
const html = await vite.transformIndexHtml(file, htmlPage)
console.log(`- sending resulting page for "${route}"`)
res.writeHead(200, { 'Content-Type': 'text/html' }).end(html)
})
}
for (const [route, file] of Object.entries(routes.dynamic)) {
app.get(route, async (req, res) => {
console.log(`Requested dynamic route "${route}":`)
let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
// Replace "./" with the absolute path of the html page
htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
console.log(`- applying vite transformations for "${file}"`)
const html = await vite.transformIndexHtml(file, htmlPage)
console.log(`- applying server transformations for "${file}"`)
const templateHtmlReq = await fetch('http://127.0.0.1:4000/api/development/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
route,
page: html,
request: {
params: req.params,
query: req.query,
},
}),
})
const renderedHtml = await templateHtmlReq.json()
console.log(`- sending resulting page for "${route}"`)
res.writeHead(200, { 'Content-Type': 'text/html' }).end(renderedHtml)
})
}
app.listen(3000, () => {
console.log(`Listening on port 3000...`)
})
}
main()
-35
View File
@@ -1,35 +0,0 @@
package config
import (
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/joho/godotenv"
)
var Slot = sl.NewSlot[*Config]()
type Config struct {
Mode string
Host string
}
func Load(l *sl.ServiceLocator) (*Config, error) {
m, err := godotenv.Read(".env")
if err != nil {
return nil, err
}
mode := "production"
if v, ok := m["MODE"]; ok {
mode = v
}
host := ":4000"
if v, ok := m["HOST"]; ok {
host = v
}
return &Config{
mode,
host,
}, nil
}
-63
View File
@@ -1,63 +0,0 @@
package database
import (
"fmt"
"git.phc.dm.unipi.it/phc/website/model"
"git.phc.dm.unipi.it/phc/website/sl"
)
var Slot = sl.NewSlot[Database]()
type Database interface {
CreateUser(user model.User) error
ReadUser(id string) (model.User, error)
ReadUsers() ([]model.User, error)
UpdateUser(id string, user model.User) error
DeleteUser(id string) error
}
type Memory struct {
Users []model.User
}
func (m *Memory) CreateUser(user model.User) error {
m.Users = append(m.Users, user)
return nil
}
func (m *Memory) ReadUser(id string) (model.User, error) {
for _, u := range m.Users {
if u.Id == id {
return u, nil
}
}
return model.User{}, fmt.Errorf(`no user with id "%s"`, id)
}
func (m *Memory) ReadUsers() ([]model.User, error) {
return m.Users, nil
}
func (m *Memory) UpdateUser(id string, user model.User) error {
for i, u := range m.Users {
if u.Id == id {
m.Users[i] = user
return nil
}
}
return fmt.Errorf(`no user with id "%s"`, id)
}
func (m *Memory) DeleteUser(id string) error {
for i, u := range m.Users {
if u.Id == id {
m.Users = append(m.Users[:i], m.Users[i+1:]...)
return nil
}
}
return fmt.Errorf(`no user with id "%s"`, id)
}
-58
View File
@@ -1,58 +0,0 @@
package articles
import (
"html/template"
"git.phc.dm.unipi.it/phc/website/services/server/dev"
"git.phc.dm.unipi.it/phc/website/services/server/router"
"git.phc.dm.unipi.it/phc/website/sl"
)
func Configure(l *sl.ServiceLocator) error {
router.UseRouteTemplatedPage(l, "/articles",
"pages/articles/index.html",
func(w dev.ResponseWriter, r dev.Request) error {
tmpl := template.New("")
tmpl, err := tmpl.Parse(string(r.Page()))
if err != nil {
return err
}
ctx := map[string]any{
"Example": "Bla bla",
}
if err := tmpl.Execute(w, ctx); err != nil {
return err
}
return nil
},
)
router.UseRouteTemplatedPage(l, "/articles/:slug",
"pages/articles/article.html",
func(w dev.ResponseWriter, r dev.Request) error {
tmpl := template.New("")
tmpl, err := tmpl.Parse(string(r.Page()))
if err != nil {
return err
}
ctx := map[string]any{
"Title": r.Param("slug"),
"Example": "Bla bla " + r.Param("slug"),
}
if err := tmpl.Execute(w, ctx); err != nil {
return err
}
return nil
},
)
return nil
}
-169
View File
@@ -1,169 +0,0 @@
package dev
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path"
"git.phc.dm.unipi.it/phc/website/services/config"
"git.phc.dm.unipi.it/phc/website/services/server/routes"
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/alecthomas/repr"
"github.com/gofiber/fiber/v2"
)
// Logger is the debug logger, in the future this will be disabled and discard by default.
var Logger *log.Logger = log.New(os.Stderr, "[services/server/dev] ", log.Lmsgprefix)
// slot represents a private "write only" service
var slot = sl.NewSlot[*devService]()
// InjectInto a [*sl.ServiceLocator] an instance of the dev service
func InjectInto(l *sl.ServiceLocator) {
sl.InjectLazy(l, slot, Configure)
}
func UseRoutesMetadata(l *sl.ServiceLocator) map[string]any {
dev, err := sl.Use(l, slot)
if err != nil {
Logger.Fatal(err)
}
return map[string]any{
"static": dev.staticRoutes,
"dynamic": dev.dynamicRoutes,
}
}
type Request interface {
Page() []byte
Param(key string) string
Query(key string) string
}
type ResponseWriter interface {
io.Writer
}
// devServerRequest is used when handling request from the dev server where params and queries are parsed by express
type devServerRequest struct {
page []byte
params map[string]string
query map[string]string
}
func (r devServerRequest) Page() []byte {
return r.page
}
func (r devServerRequest) Param(key string) string {
return r.params[key]
}
func (r devServerRequest) Query(key string) string {
return r.query[key]
}
// Handler is a custom routes handler
type Handler func(ResponseWriter, Request) error
type devService struct {
staticRoutes map[string]string
dynamicRoutes map[string]string
dynamicRoutesHandlers map[string]Handler
}
func Configure(l *sl.ServiceLocator) (*devService, error) {
d := &devService{
map[string]string{},
map[string]string{},
map[string]Handler{},
}
r, err := sl.Use(l, routes.Root)
if err != nil {
return nil, err
}
config, _ := sl.Use(l, config.Slot)
if config.Mode != "development" {
return d, nil
}
r.Get("/api/development/routes", func(c *fiber.Ctx) error {
return c.JSON(map[string]any{
"static": d.staticRoutes,
"dynamic": d.dynamicRoutes,
})
})
r.Post("/api/development/render", func(c *fiber.Ctx) error {
var data struct {
Route string `json:"route"`
HtmlPage string `json:"page"`
Request struct {
ParamsMap map[string]string `json:"params"`
QueryMap map[string]string `json:"query"`
} `json:"request"`
}
if err := c.BodyParser(&data); err != nil {
return err
}
Logger.Printf(`server rendering route "%s"`, data.Route)
Logger.Printf(`- params: %s`, repr.String(data.Request.ParamsMap))
Logger.Printf(`- query: %s`, repr.String(data.Request.QueryMap))
handler, ok := d.dynamicRoutesHandlers[data.Route]
if !ok {
return fmt.Errorf(`no handler for "%s"`, data.Route)
}
var buf bytes.Buffer
if err := handler(&buf, devServerRequest{
[]byte(data.HtmlPage),
data.Request.ParamsMap,
data.Request.QueryMap,
}); err != nil {
return err
}
return c.JSON(buf.String())
})
return d, nil
}
// RegisterRoute will register the provided "mountPoint" to the "frontendHtml" page
func RegisterRoute(l *sl.ServiceLocator, mountPoint, frontendFile string) {
dev, err := sl.Use(l, slot)
if err != nil {
Logger.Fatal(err)
}
dev.staticRoutes[mountPoint] = frontendFile
Logger.Printf(`registered vite route "%v" for "%v"`, frontendFile, mountPoint)
}
func RegisterDynamicRoute(l *sl.ServiceLocator, mountPoint, frontendFile string, handler Handler) {
dev, err := sl.Use(l, slot)
if err != nil {
Logger.Fatal(err)
}
dev.dynamicRoutes[mountPoint] = frontendFile
dev.dynamicRoutesHandlers[mountPoint] = handler
Logger.Printf(`registered vite route "%v" for "%v"`, frontendFile, mountPoint)
}
func GetArtifactPath(frontendFile string) string {
return path.Join("./out/frontend/", frontendFile)
}
@@ -1,35 +0,0 @@
package listautenti
import (
"git.phc.dm.unipi.it/phc/website/services/database"
"git.phc.dm.unipi.it/phc/website/services/server/router"
"git.phc.dm.unipi.it/phc/website/services/server/routes"
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/gofiber/fiber/v2"
)
func Configure(l *sl.ServiceLocator) error {
router.UseRoutePage(l, "/utenti", "pages/lista-utenti/index.html")
db, err := sl.Use(l, database.Slot)
if err != nil {
return err
}
r, err := sl.Use(l, routes.Root)
if err != nil {
return err
}
r.Get("/api/lista-utenti", func(c *fiber.Ctx) error {
users, err := db.ReadUsers()
if err != nil {
return err
}
return c.JSON(users)
})
return nil
}
@@ -1,78 +0,0 @@
package listautenti_test
import (
"context"
"fmt"
"io"
"net"
"net/http"
"testing"
"git.phc.dm.unipi.it/phc/website/model"
"git.phc.dm.unipi.it/phc/website/services/database"
"git.phc.dm.unipi.it/phc/website/services/server/routes"
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
)
func Test1(t *testing.T) {
l := sl.New()
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{
{
Id: "claire",
FullName: "Claire Doe",
Nickname: "claire-doe",
AuthSources: map[string]model.AuthSource{},
},
{
Id: "john",
FullName: "John Smith",
Nickname: "john-smith",
AuthSources: map[string]model.AuthSource{},
},
},
})
r := fiber.New()
sl.InjectValue(l, routes.Root, fiber.Router(r))
req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil)
if err != nil {
t.Error(err)
}
ln := fasthttputil.NewInmemoryListener()
defer ln.Close()
go func() {
err := fasthttp.Serve(ln, r.Handler())
if err != nil {
panic(fmt.Errorf("failed to serve: %v", err))
}
}()
client := http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return ln.Dial()
},
},
}
res, err := client.Do(req)
if err != nil {
t.Error(err)
}
body, err := io.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(body))
}
-73
View File
@@ -1,73 +0,0 @@
package router
import (
"bytes"
"log"
"os"
"git.phc.dm.unipi.it/phc/website/services/server/dev"
"git.phc.dm.unipi.it/phc/website/services/server/routes"
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/gofiber/fiber/v2"
)
// assert type of [ServerRequest] is [dev.Request]
var _ dev.Request = ServerRequest{}
// ServerRequest is used when the request is directly for the Go server
type ServerRequest struct {
page []byte
fiberContext *fiber.Ctx
}
func (r ServerRequest) Page() []byte {
return r.page
}
func (ctx ServerRequest) Param(key string) string {
return ctx.fiberContext.Params(key)
}
func (ctx ServerRequest) Query(key string) string {
return ctx.fiberContext.Query(key)
}
func UseRoutePage(l *sl.ServiceLocator, route, frontendFile string) {
root, err := sl.Use(l, routes.Root)
if err != nil {
log.Fatal(err)
}
dev.RegisterRoute(l, route, frontendFile)
root.Get(route, func(c *fiber.Ctx) error {
return c.SendFile(dev.GetArtifactPath(frontendFile))
})
}
func UseRouteTemplatedPage(l *sl.ServiceLocator, route, frontendFile string, handler dev.Handler) {
r, err := sl.Use(l, routes.Root)
if err != nil {
log.Fatal(err)
}
dev.RegisterDynamicRoute(l, route, frontendFile, handler)
r.Get(route, func(c *fiber.Ctx) error {
rawPage, err := os.ReadFile(dev.GetArtifactPath(frontendFile))
if err != nil {
return err
}
var buf bytes.Buffer
if err := handler(&buf, ServerRequest{
rawPage,
c,
}); err != nil {
return err
}
return c.Type(".html").Send(buf.Bytes())
})
}
-9
View File
@@ -1,9 +0,0 @@
package routes
import (
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/gofiber/fiber/v2"
)
var Root = sl.NewSlot[fiber.Router]()
-30
View File
@@ -1,30 +0,0 @@
package server
import (
"git.phc.dm.unipi.it/phc/website/services/server/articles"
"git.phc.dm.unipi.it/phc/website/services/server/dev"
"git.phc.dm.unipi.it/phc/website/services/server/listautenti"
"git.phc.dm.unipi.it/phc/website/services/server/routes"
"git.phc.dm.unipi.it/phc/website/sl"
"github.com/gofiber/fiber/v2"
)
type Server struct{ Router *fiber.App }
func Configure(l *sl.ServiceLocator) (*Server, error) {
r := fiber.New(fiber.Config{})
r.Static("/assets", "./out/frontend/assets")
sl.InjectValue(l, routes.Root, fiber.Router(r))
dev.InjectInto(l)
if err := listautenti.Configure(l); err != nil {
return nil, err
}
if err := articles.Configure(l); err != nil {
return nil, err
}
return &Server{r}, nil
}
-127
View File
@@ -1,127 +0,0 @@
// The [sl] package has two main concepts, the [ServiceLocator] itself is the main object that one should pass around through the application. A [ServiceLocator] has a list of slots that can be filled with [InjectLazy] and [InjectValue] and retrieved with [Use]. As slots should be unique they can only be created with the [NewSlot] function.
//
// The usual way to use this module is to make slots for go interfaces and then pass implementations using the [InjectValue] and [InjectLazy] functions.
//
// Services can be of various types:
// - a service with no dependencies can be directly injected inside a ServiceLocator using [InjectValue].
// - a service with dependencies on other service should use [InjectLazy]. This lets the service to initialize itself when required and makes the developer not think the topological sort to put onto the DAG of service dependencies.
// - a service can also be private, in this case the slot for a service should be a private field in the service package. This kind of services should also provide a way to inject them into a ServiceLocator.
// - a package also just provide a slot. This is useful for using the ServiceLocator to easily pass around values, effectively threating slots just as dynamically scoped variables.
package sl
import (
"fmt"
"log"
"os"
)
// Logger is the debug logger, in the future this will be disabled and discard by default.
//
// As this is the service locator module it was meaning less to pass this through the ServiceLocator itself (without making the whole module more complex)
var Logger *log.Logger = log.New(os.Stderr, "[sl] ", log.Lmsgprefix)
// slot is just a "typed" unique "symbol".
type slot[T any] *struct{}
// NewSlot is the only way to create instances of the slot type. Each instance is unique.
//
// This then lets you attach a service instance of type "T" to a [ServiceLocator] object.
func NewSlot[T any]() slot[T] {
return slot[T](new(struct{}))
}
// slotEntry represents a service that can lazily initialized (using "createFunc"). Once initialized the instance is kept in the "value" field. The field "typeName" just for debugging purposes.
type slotEntry struct {
createFunc func(*ServiceLocator) (any, error)
created bool
value any
typeName string
}
func (s *slotEntry) checkInitialized(l *ServiceLocator) error {
if !s.created {
v, err := s.createFunc(l)
if err != nil {
return err
}
Logger.Printf(`initialized lazy value of type %T for slot of type %s`, v, s.typeName)
s.created = true
s.value = v
}
return nil
}
// ServiceLocator is the main context passed around to retrive service instances, the interface uses generics so to inject and retrive service instances you should use the functions [InjectValue], [InjectLazy] and [Use].
type ServiceLocator struct {
providers map[any]*slotEntry
}
// New creates a new [ServiceLocator] context to pass around in the application.
func New() *ServiceLocator {
return &ServiceLocator{
providers: map[any]*slotEntry{},
}
}
// InjectValue will inject a concrete instance inside the ServiceLocator "l" for the given "slotKey". This should be used for injecting "static" services, for instances whose construction depend on other services you should use the [InjectLazy] function.
//
// This is generic over "T" to check that instances for the given slot type check as "T" can also be an interface.
func InjectValue[T any](l *ServiceLocator, slotKey slot[T], value T) T {
Logger.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]())
l.providers[slotKey] = &slotEntry{
nil,
true,
value,
getTypeName[T](),
}
return value
}
// InjectLazy will inject an instance inside the given ServiceLocator and "slotKey" that is created only when requested with a call to the [Use] function.
//
// This is generic over "T" to check that instances for the given slot type check as "T" can also be an interface.
func InjectLazy[T any](l *ServiceLocator, slotKey slot[T], createFunc func(*ServiceLocator) (T, error)) {
Logger.Printf(`injected lazy for slot of type %s`, getTypeName[T]())
l.providers[slotKey] = &slotEntry{
createFunc: func(l *ServiceLocator) (any, error) {
return createFunc(l)
},
created: false,
value: nil,
typeName: getTypeName[T](),
}
}
// Use retrieves the value of type T associated with the given slot key from the provided ServiceLocator instance.
//
// If the ServiceLocator does not have a value for the slot key, or if the value wasn't correctly initialized (in the case of a lazy slot), an error is returned.
func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) {
var zero T
slot, ok := l.providers[slotKey]
if !ok {
return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]())
}
err := slot.checkInitialized(l)
if err != nil {
return zero, err
}
v := slot.value.(T)
Logger.Printf(`using slot of type %s with value of type %T`, getTypeName[T](), v)
return v, nil
}
// getTypeName is a trick to get the name of a type (even if it is an interface type)
func getTypeName[T any]() string {
var zero T
return fmt.Sprintf(`%T`, &zero)[1:]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+30
View File
@@ -0,0 +1,30 @@
/**
* @typedef {{
* image?: string,
* course?: string,
* title?: string,
* author: string,
* courseYear: string
* }} AppuntiCardProps
*/
/**
*
* @param {AppuntiCardProps} param0
* @returns
*/
export const AppuntiCard = ({ course, title, author, courseYear }) => {
return (
<div class="appunti-item">
<div class="thumbnail"></div>
{title && <div class="title">{title}</div>}
{course && <div class="course">{course}</div>}
<div class="author">@{author}</div>
<div class="course-year">{courseYear}</div>
</div>
)
}
export const AppuntiList = ({ children }) => {
return <div class="appunti-list">{children}</div>
}
+65
View File
@@ -0,0 +1,65 @@
import { type ComponentChildren } from 'preact'
import { useState, useRef, useEffect } from 'preact/hooks'
import { clsx, isMobile } from './lib/util'
export const ComboBox = ({
value,
setValue,
children,
}: {
value: string
setValue: (s: string) => void
children: Record<string, ComponentChildren>
}) => {
const [cloak, setCloak] = useState(true)
const [open, setOpen] = useState(true)
const comboRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (comboRef.current && !comboRef.current.contains(e.target as Node)) {
setOpen(false)
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [])
const [itemWidth, setItemWidth] = useState<number>(200)
useEffect(() => {
setOpen(false)
setCloak(false)
}, [])
return (
<div
class="combobox"
ref={comboRef}
style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}
>
<div class="selected" onClick={() => setOpen(!open)}>
<div class="content">{children[value]}</div>
<span class="material-symbols-outlined">expand_more</span>
</div>
{open && (
<div
class={clsx('dropdown', cloak && 'invisible')}
ref={el => el && setItemWidth(el.offsetWidth)}
>
{Object.keys(children).map(key => (
<div
class="option"
onClick={() => {
setValue(key)
setOpen(false)
}}
>
{children[key]}
</div>
))}
</div>
)}
</div>
)
}
+13
View File
@@ -0,0 +1,13 @@
import { useState } from 'preact/hooks'
export const Counter = ({}) => {
const [count, setCount] = useState(0)
return (
<div class="counter">
<button onClick={() => setCount(value => value - 1)}>-</button>
<div class="value">{count}</div>
<button onClick={() => setCount(value => value + 1)}>+</button>
</div>
)
}
+29
View File
@@ -0,0 +1,29 @@
import { useComputed, useSignal, type ReadonlySignal } from '@preact/signals'
import type { JSX } from 'preact/jsx-runtime'
export const ShowMore = <T extends any>({
items,
pageSize,
children,
}: {
items: ReadonlySignal<T[]>
pageSize: number
children: (item: T) => JSX.Element
}) => {
const $shownItems = useSignal(pageSize)
const $paginatedItems = useComputed(() => {
return items.value.slice(0, $shownItems.value)
})
return (
<>
{$paginatedItems.value.map(children)}
<div class="show-more">
{$shownItems.value < items.value.length && (
<button onClick={() => ($shownItems.value += pageSize)}>Mostra altri</button>
)}
</div>
</>
)
}
+155
View File
@@ -0,0 +1,155 @@
import { useComputed, useSignal } from '@preact/signals'
import Fuse from 'fuse.js'
import { useEffect } from 'preact/hooks'
import { ShowMore } from './Paginate'
import { ComboBox } from './ComboBox'
type User = {
uid: string
gecos: string
}
const FILTERS = {
utenti: {
icon: 'person',
label: 'Utenti',
},
macchinisti: {
icon: 'construction',
label: 'Macchinisti',
},
rappstud: {
icon: 'account_balance',
label: 'Rappresentanti',
},
}
function applyPatches(users: User[]) {
users.forEach(user => {
// strip ",+" from the end of the gecos field
user.gecos = user.gecos.replace(/,+$/, '')
// capitalize the first letter of each word
user.gecos = user.gecos.replace(/\b\w/g, c => c.toUpperCase())
})
// reverse the order of the users
users.reverse()
}
const MACCHINISTI = ['delucreziis', 'minnocci', 'baldino', 'manicastri', 'llombardo', 'serdyuk']
const RAPPSTUD = [
'smannella',
'lotti',
'rotolo',
'saccani',
'carbone',
'mburatti',
'ppuddu',
'marinari',
'evsilvestri',
'tateo',
'graccione',
'dilella',
'rocca',
'odetti',
'borso',
'numero',
]
export const UtentiPage = () => {
const $utentiData = useSignal<User[]>([])
const $filter = useSignal('utenti')
const $filteredData = useComputed(() =>
$filter.value === 'macchinisti'
? $utentiData.value.filter(user => MACCHINISTI.includes(user.uid))
: $filter.value === 'rappstud'
? $utentiData.value.filter(user => RAPPSTUD.includes(user.uid))
: $utentiData.value
)
const $fuse = useComputed(
() =>
new Fuse($filteredData.value, {
keys: ['gecos', 'uid'],
})
)
const $searchText = useSignal('')
const $searchResults = useComputed(() =>
$searchText.value.trim().length > 0
? $fuse.value?.search($searchText.value).map(result => result.item) ?? []
: $filteredData.value
)
useEffect(() => {
fetch('https://poisson.phc.dm.unipi.it/users.json')
.then(response => response.json())
.then(data => {
applyPatches(data)
$utentiData.value = data
$fuse.value.setCollection(data)
})
}, [])
return (
<>
<div class="search-bar">
<ComboBox value={$filter.value} setValue={s => ($filter.value = s)}>
{Object.fromEntries(
Object.entries(FILTERS).map(([k, v]) => [
k,
<>
<span class="material-symbols-outlined">{v.icon}</span> {v.label}
</>,
])
)}
</ComboBox>
<div class="search">
<input
type="text"
placeholder="Cerca un utente Poisson..."
onInput={e => ($searchText.value = e.currentTarget.value)}
value={$searchText.value}
/>
<span class="material-symbols-outlined">search</span>
</div>
</div>
<div class="search-results">
{$searchResults.value ? (
<ShowMore items={$searchResults} pageSize={100}>
{poissonUser => (
<div class="search-result">
<div class="icon">
<span class="material-symbols-outlined">
{RAPPSTUD.includes(poissonUser.uid)
? 'account_balance'
: MACCHINISTI.includes(poissonUser.uid)
? 'construction'
: 'person'}
</span>
</div>
<div class="text">{poissonUser.gecos}</div>
<div class="right">
<a
href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`}
target="_blank"
>
<span class="material-symbols-outlined">open_in_new</span>
</a>
</div>
</div>
)}
</ShowMore>
) : (
<>Nessun risultato</>
)}
</div>
</>
)
}
View File
+69
View File
@@ -0,0 +1,69 @@
import { useEffect, useState } from 'preact/hooks'
export const trottleDebounce = <T extends any[], R>(
fn: (...args: T) => R,
delay: number,
options: { leading?: boolean; trailing?: boolean } = {}
): ((...args: T) => R | undefined) => {
let lastCall = 0
let lastResult: R | undefined
let lastArgs: T | undefined
let timeout: NodeJS.Timeout | undefined
const leading = options.leading ?? true
const trailing = options.trailing ?? true
return (...args: T): R | undefined => {
lastArgs = args
if (leading && Date.now() - lastCall >= delay) {
lastCall = Date.now()
lastResult = fn(...args)
} else {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
if (trailing && lastArgs) {
lastCall = Date.now()
lastResult = fn(...lastArgs)
}
}, delay)
}
return lastResult
}
}
export type ClassValue = string | ClassValue[] | Record<string, boolean> | false | undefined
export function clsx(...args: ClassValue[]): string {
return args
.flatMap(arg => {
if (typeof arg === 'string') {
return arg
} else if (Array.isArray(arg)) {
return clsx(...arg)
} else if (typeof arg === 'boolean') {
return []
} else if (typeof arg === 'object') {
return Object.entries(arg).flatMap(([key, value]) => (value ? key : []))
} else {
return []
}
})
.join(' ')
}
export const isMobile = () => {
const [windowWidth, setWindowWidth] = useState(0)
useEffect(() => {
setWindowWidth(window.innerWidth)
const handleResize = () => setWindowWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return windowWidth < 1024
}
+15
View File
@@ -0,0 +1,15 @@
---
import type { AstroBuiltinProps } from 'astro'
import type { AstroComponentFactory } from 'astro/runtime/server/index.js'
type Props = {
large?: boolean
style?: string
}
const { large, ...props } = Astro.props
---
<div class:list={['card', large && 'large']} {...props}>
<slot />
</div>
+9
View File
@@ -0,0 +1,9 @@
<footer>
<div class="text">
<p>
&copy; PHC 2024 &nbsp;&bull;&nbsp; <a href="mailto:macchinisti@lists.dm.unipi.it"
>macchinisti@lists.dm.unipi.it</a
>
</p>
</div>
</footer>
+32
View File
@@ -0,0 +1,32 @@
<header>
<a href="/" class="logo">
<img src="/images/phc-logo-2024-11@x8.png" alt="phc logo" />
</a>
<input type="checkbox" id="header-menu-toggle" />
<div class="links desktop-only">
<a role="button" href="/utenti">Utenti</a>
<!-- <a role="button" href="/appunti">Appunti</a> -->
<a role="button" href="/notizie">Notizie</a>
<a role="button" href="/guide">Guide</a>
<a role="button" href="/storia">Storia</a>
<!-- <a class="primary" role="button" href="/login">Login</a> -->
</div>
<div class="mobile-only">
<label id="header-menu-toggle-menu" role="button" class="flat icon" for="header-menu-toggle">
<span class="material-symbols-outlined">menu</span>
</label>
<label id="header-menu-toggle-close" role="button" class="flat icon" for="header-menu-toggle">
<span class="material-symbols-outlined">close</span>
</label>
</div>
<div class="side-menu">
<div class="links">
<a role="button" href="/utenti">Utenti</a>
<!-- <a role="button" href="/appunti">Appunti</a> -->
<a role="button" href="/notizie">Notizie</a>
<a role="button" href="/guide">Guide</a>
<a role="button" href="/storia">Storia</a>
<!-- <a class="primary" role="button" href="/login">Login</a> -->
</div>
</div>
</header>
+26
View File
@@ -0,0 +1,26 @@
---
type Props = {
color: string
}
const { color } = Astro.props
const patternId = 'zig-zag-' + color.slice(1)
---
<div class="zig-zag">
<svg
width="100%"
height="2rem"
viewBox="0 0 1 1"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMinYMid meet"
>
<defs>
<pattern id={patternId} x="0" y="0" width="2" height="1" patternUnits="userSpaceOnUse">
<path fill={color} d="M 0,1 L 1,0 L 2,1 L 0,1"></path>
</pattern>
</defs>
<rect fill={`url(#${patternId})`} x="0" y="0" width="1000" height="1"></rect>
</svg>
</div>
+24
View File
@@ -0,0 +1,24 @@
---
// Card.astro
export interface Props {
title: string
href: string
imgSrc?: string
style?: string
}
const { href, imgSrc, style, title } = Astro.props
---
<a target="_blank" href={href} style={style}>
<div class="project">
<div class="image">
{imgSrc ? <img src={imgSrc} alt={'logo for ' + title.toLowerCase()} /> : <div class="box" />}
</div>
<div class="title">{title}</div>
<div class="description">
<slot />
</div>
</div>
</a>
+14
View File
@@ -0,0 +1,14 @@
---
const { year, title } = Astro.props
---
<div class="timeline-item">
<div class="content">
<div class="card">
<div class="title">{year} &bull; {title}</div>
<div class="text">
<slot />
</div>
</div>
</div>
</div>
@@ -0,0 +1,14 @@
---
import { JSDOM } from 'jsdom'
import Container from './Container.astro'
const language = Astro.props['data-language'] ?? 'text'
const html = await Astro.slots.render('default')
const rawCode = new JSDOM(html).window.document.body.textContent
---
<pre {...Astro.props}><slot /></pre>
{language === 'astro' && <Container set:html={rawCode} />}
+9
View File
@@ -0,0 +1,9 @@
---
const { size, ...rest } = Astro.props
---
<div class:list={['container', size ?? 'normal']} {...rest}>
<div class="content">
<slot />
</div>
</div>
+20
View File
@@ -0,0 +1,20 @@
---
type Props = {
colors: string[]
}
const { colors } = Astro.props
---
<div class="palette">
{
colors.map(value => (
<>
<div class="color">
<div class="region" style={{ backgroundColor: value }} />
</div>
<div class="label">{value}</div>
</>
))
}
</div>
+52
View File
@@ -0,0 +1,52 @@
import { z, defineCollection } from 'astro:content'
// Una notizia ha una data di pubblicazione ma non ha un autore in quanto sono
// notizie generiche sul PHC e non sono scritte da un autore specifico
const newsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
publishDate: z.date(),
image: z
.object({
url: z.string(),
alt: z.string(),
})
.optional(),
}),
})
// Una guida ha un autore ma non ha una data di pubblicazione in quanto è un
// contenuto statico e non è importante sapere quando è stata pubblicata
const guidesCollection = defineCollection({
type: 'content',
schema: z.object({
id: z.string(),
title: z.string(),
description: z.string(),
author: z.string(),
series: z.string().optional(),
tags: z.array(z.string()),
}),
})
// Per ora sono su un sito a parte ma prima o poi verranno migrati qui
const seminariettiCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
author: z.string(),
publishDate: z.date(),
eventDate: z.date(),
tags: z.array(z.string()),
}),
})
// Export a single `collections` object to register your collection(s)
export const collections = {
news: newsCollection,
guides: guidesCollection,
seminarietti: seminariettiCollection,
}
+353
View File
@@ -0,0 +1,353 @@
---
id: git-101
title: Git 101
description: Una guida introduttiva alle basi di Git
author: Luca Lombardo
tags: [git, gitea]
---
Git è un sistema di controllo di versione distribuito creato per gestire progetti di qualsiasi dimensione, mantenendo traccia delle modifiche al codice sorgente. Questa guida ci accompagnerà dai concetti di base fino alle funzionalità avanzate.
---
## **1. Introduzione a Git**
### **Cos'è Git?**
- **Sistema di controllo di versione**: Gestisce le modifiche al codice sorgente nel tempo.
- **Distribuito**: Ogni sviluppatore ha una copia del repository.
- **Veloce e leggero**: Ottimizzato per la velocità e le prestazioni.
### **Perché usare Git?**
- **Tracciabilità**: Ogni modifica è tracciata e reversibile.
- **Collaborazione**: Più persone possono lavorare sullo stesso progetto.
- **Backup**: Repository remoto per il backup del codice.
- **Branching**: Lavoriamo su nuove funzionalità senza influenzare il codice principale.
---
## **2. Installazione**
> Se ci troviamo al dipartimento di matematica a Pisa, è già installato su tutte le macchine dell'aula 3 ed aula 4!
### **Windows**
1. Scarichiamo [Git for Windows](https://git-scm.com/download/win).
2. Seguiamo il wizard di installazione.
3. Durante l'installazione:
- Selezioniamo "Git Bash" come terminale.
- Configuriamo un editor di testo (es. Vim o Nano).
### **macOS**
1. Usiamo `brew` per installare Git:
```bash
brew install git
```
### **Linux**
1. Installiamo Git usando il nostro gestore di pacchetti:
- **Debian/Ubuntu**:
```bash
sudo apt update
sudo apt install git
```
- **Arch Linux**:
```bash
sudo pacman -S git
```
---
## **3. Configurazione iniziale**
Una volta installato, configuriamo Git con il nostro nome e indirizzo email:
```bash
git config --global user.name "Il Nostro Nome"
git config --global user.email "nostro@email.com"
```
### **Verifica configurazione**
```bash
git config --list
```
---
## **4. Concetti fondamentali**
### **Repository**
- **Repository locale**: Una cartella sul nostro computer che contiene il nostro progetto.
- **Repository remoto**: Una versione del progetto ospitata su un server (es. GitHub, GitLab).
### **Branch**
Un ramo permette di lavorare su modifiche isolate rispetto al codice principale (branch `main` o `master`).
### **Commit**
Una snapshot del nostro codice in un determinato momento.
---
## **5. Creazione e gestione di un repository**
### **Inizializzare un nuovo repository**
Se stiamo iniziando un nuovo progetto, possiamo creare un nuovo repository con il comando:
```bash
git init
```
### **Clonare un repository esistente**
Se invece vogliamo lavorare su un progetto esistente, possiamo clonare da remoto con:
```bash
git clone <URL>
```
---
## **6. Lavorare con Git**
### **Aggiungere file**
Aggiungiamo file allo stage per includerli nel prossimo commit:
```bash
git add <nome-file>
# Oppure, per aggiungere tutti i file:
git add .
```
---
### **Commit**
Il comando **`git commit`** è utilizzato per registrare le modifiche nel repository locale. Ogni commit è una snapshot del progetto, contenente tutte le modifiche che sono state aggiunte tramite `git add`.
#### Come funziona:
1. **Aggiungiamo modifiche all'area di staging**:
Prima di fare un commit, dobbiamo aggiungere i file che vogliamo includere al prossimo commit usando `git add`. Questo comando prepara i file per essere salvati nella cronologia del repository.
Esempio:
```bash
git add <nome-file>
# Oppure per aggiungere tutti i file modificati
git add .
```
2. **Effettuiamo il commit**:
Una volta che i file sono nell'area di staging, possiamo fare un commit. Ogni commit dovrebbe avere un messaggio descrittivo che spieghi cosa è stato cambiato nel progetto.
Comando per fare un commit:
```bash
git commit -m "Descrizione chiara del cambiamento"
```
L'opzione `-m` permette di aggiungere il messaggio direttamente dalla linea di comando. Se omettiamo `-m`, Git aprirà un editor di testo per scrivere il messaggio di commit.
#### Cosa succede dietro le quinte:
- Git salva lo stato dei file nell'area di staging in un commit, che viene aggiunto alla cronologia del repository locale.
- Ogni commit ha un identificatore unico (hash) che consente di risalire facilmente alle modifiche in qualsiasi momento.
---
### **Push**
**`git push`** è il comando che ci permette di inviare le modifiche dal nostro repository locale a un repository remoto (ad esempio su GitHub, GitLab, Bitbucket, ecc.).
#### Come funziona:
1. Dopo aver fatto uno o più commit locali, dobbiamo inviare queste modifiche al repository remoto.
2. Per fare questo, usiamo il comando **`git push`** seguito dal nome del remoto (di solito `origin` per il repository remoto di default) e dal nome del branch (di solito `main` o `master`, ma potrebbe essere qualsiasi altro nome di branch che stiamo utilizzando).
Comando per inviare le modifiche:
```bash
git push origin main
```
#### Cosa succede dietro le quinte:
- Git confronta il nostro branch locale con il branch remoto. Se ci sono nuovi commit nel branch remoto che non sono ancora nel nostro branch locale, ci verrà richiesto di fare un **pull** per aggiornare prima di fare il push.
- Il nostro repository locale viene sincronizzato con il remoto, rendendo le modifiche visibili a tutti gli altri che hanno accesso al repository remoto.
#### Errori comuni:
- Se il repository remoto è stato aggiornato nel frattempo da qualcun altro (ad esempio, con un altro push), riceveremo un errore che ci avvisa che dobbiamo fare prima un `git pull` per sincronizzare il nostro lavoro.
---
### **Pull**
**`git pull`** è il comando che ci permette di scaricare e integrare le modifiche dal repository remoto al nostro repository locale. È una combinazione di due comandi: **`git fetch`** (scarica i cambiamenti dal remoto) e **`git merge`** (integra questi cambiamenti nel nostro branch attuale).
#### Come funziona:
1. Se altri collaboratori hanno fatto modifiche al repository remoto, possiamo ottenere queste modifiche con **`git pull`**. Questo comando aggiorna il nostro branch locale con le modifiche più recenti dal repository remoto.
2. Eseguiamo il comando:
```bash
git pull origin main
```
In questo caso, `origin` è il nome del repository remoto (il nome predefinito quando cloni un repository), e `main` è il branch che vogliamo aggiornare.
#### Cosa succede dietro le quinte:
- **`git fetch`** scarica tutte le modifiche dal repository remoto, ma non le integra ancora nel nostro codice.
- **`git merge`** unisce le modifiche scaricate al nostro branch attuale, risolvendo eventuali conflitti, se necessario.
#### Errori comuni:
- Se ci sono conflitti tra il nostro lavoro e quello degli altri, Git ci avviserà che dovremo risolverli manualmente. Dopo aver risolto i conflitti, dovremo aggiungere i file risolti (`git add`) e completare il merge con un commit.
## **7. Lavorare con branch**
### Creare un nuovo branch
Per creare un nuovo branch in Git, utilizziamo il comando:
```bash
git branch <nome-branch>
```
Sostituiamo `<nome-branch>` con il nome desiderato per il nuovo branch.
### Spostarsi su un branch
Per spostarci su un branch esistente, usiamo:
```bash
git checkout <nome-branch>
```
Oppure, per creare e spostarci su un nuovo branch in un solo comando:
```bash
git switch -c <nome-branch>
```
### Unire un branch nel branch principale
Per unire un branch nel branch principale (di solito chiamato `main`):
1. Spostiamoci sul branch principale:
```bash
git checkout main
```
2. Eseguiamo il merge del branch desiderato:
```bash
git merge <nome-branch>
```
Sostituiamo `<nome-branch>` con il nome del branch che vogliamo unire.
### Risoluzione dei conflitti
Quando due persone modificano lo stesso file, Git può generare un conflitto. Ecco come risolverlo:
1. Identifichiamo il file in conflitto:
```bash
git status
```
2. Modifichiamo manualmente il file per risolvere il conflitto. Cerchiamo i segni di conflitto (`<<<<<<<`, `=======`, `>>>>>>>`) e scegliamo quali modifiche mantenere.
3. Aggiungiamo il file risolto allo stage:
```bash
git add <file>
```
4. Concludiamo con un commit per salvare le modifiche risolte:
```bash
git commit
```
---
## **9. Comandi utili**
### **Visualizzare le differenze**
```bash
git diff
```
### **Annullare modifiche**
1. **Prima del commit**:
```bash
git checkout -- <file>
```
2. **Dopo il commit**:
```bash
git reset --soft HEAD~1
```
### **Eliminare un branch**
```bash
git branch -d <nome-branch>
```
---
## **10. Best practices**
- Scriviamo messaggi di commit chiari e descrittivi.
- Creiamo branch per nuove funzionalità o bugfix.
- Sincronizziamo frequentemente il nostro repository locale con quello remoto.
---
## **11. Risorse aggiuntive**
- [Documentazione ufficiale di Git](https://git-scm.com/doc)
- [Guida interattiva Learn Git Branching](https://learngitbranching.js.org/)
- [GitHub Docs](https://docs.github.com/)
@@ -0,0 +1,80 @@
---
id: pagina-poisson-con-astro
title: Pagina Poisson con Astro
description: Vediamo come creare una pagina Poisson moderna con Astro
author: Antonio De Lucreziis
tags: [astro, website]
---
In questa guida vedremo come creare una pagina Poisson moderna utilizzando Astro, un nuovo framework di sviluppo web statico. Per prima cosa installeremo NodeJS sul nostro computer, poi creeremo un nuovo progetto Astro e infine dopo averlo generato, lo caricheremo su Poisson.
## Setup
Se siete sul vostro pc installate VSCode ed il plugin di Astro.
Poi installiamo NodeJS (se siete su Windows è consigliato [installare WSL con `wsl --install`](https://learn.microsoft.com/en-us/windows/wsl/install) e poi installare i seguenti pacchetti nell'ambiente Linux)
> NodeJS: https://nodejs.org/en/download/package-manager
```bash
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm use --install-if-missing 20
node -v
npm -v
```
## Creazione di un nuovo progetto Astro
Per prima cosa dobbiamo creare un nuovo progetto di Astro sul nostro computer, possiamo scegliere [uno dei tanti temi disponibili per Astro](https://astro.build/themes/) o partire da un blog di esempio con il seguente comando:
```bash
npm create astro@latest -- --template blog
cd nome-del-progetto
npm install
```
Se ad esempio volessimo usare un tema come "[Astro Nano](https://github.com/markhorn-dev/astro-nano)" possiamo fare così:
```bash
git clone https://github.com/markhorn-dev/astro-nano sito-poisson
cd sito-poisson
npm install
```
L'ultima cosa importante che c'è da cambiare è che le pagine Poisson sono hostate su `https://poisson.phc.dm.unipi.it/~nomeutente/` che non è la radice del dominio del sito. Quindi dobbiamo cambiare il file `astro.config.mjs`:
```javascript
export default defineConfig({
...
base: '/~nomeutente/',
trailingSlash: 'always',
...
});
```
## Lavorare con Astro
Per vedere il nostro progetto in locale possiamo eseguire il comando:
```bash
npm run dev
```
A questo punto in base al tema scelto possiamo modificare i file dentro `src/pages` per cambiare il contenuto delle pagine. Molti temi sono preimpostati per scrivere contenuti in Markdown, ad esempio per il _template blog_ possiamo scrivere gli articoli per il nostro blog in `src/content/blog/{nome_post}.md`.
## Appunti
Una volta creato il progetto possiamo caricare appunti e dispense nella cartella `/public`
## Deploy
Per caricare il nostro sito su Poisson possiamo usare il comando `rsync`:
```bash
npm run build
rsync -avz dist/ username@poisson.phc.dm.unipi.it:public_html/
```
Dove `username` è il nostro username Poisson. Da notare che gli `/` alla fine di `dist/` e `public_html/` sono importanti per evitare di creare delle cartelle per errore.
@@ -0,0 +1,111 @@
---
id: deploy-with-github-actions
title: Deploy automatico per Poisson da GitHub
description: Come impostare il deploy automatico per la propria pagina Poisson utilizzando le GitHub Actions
author: Antonio De Lucreziis
tags: [github, deploy, poisson]
---
Supponiamo di avere un sito web statico che vogliamo caricare su Poisson, ad esempio un progetto NodeJS che genera in `dist/` o `out/` i file da caricare. Come possiamo automatizzare il processo di deploy su Poisson?
Vediamo come automatizzare questo processo utilizzando le GitHub Actions.
## Setup
Manualmente, possiamo costruire il nostro progetto in locale e poi caricare i file su Poisson utilizzando `rsync`, ad esempio come segue:
```bash
$ npm run build
$ rsync -avz dist/ <username>@poisson.phc.dm.unipi.it:public_html/
```
(osserviamo che gli `/` alla fine di `dist/` e `public_html/` sono importanti per evitare di creare delle cartelle per errore)
## GitHub Actions
Per automatizzare questo processo possiamo caricare il nostro progetto su GitHub ed aggiungere un _workflow_ che esegue il build e il deploy ogni volta che facciamo un push sul branch `main`. Ad esempio, possiamo creare un file `.github/workflows/deploy-poison.yaml` con quanto segue:
```yaml
name: Deploy to Poisson
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Write SSH keys
run: |
install -m 600 -D /dev/null ~/.ssh/known_hosts
install -m 600 -D /dev/null ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '23'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Deploy
run: rsync -cavz dist/ ${{ secrets.SSH_USER }}@poisson.phc.dm.unipi.it:public_html/
```
## Comando rsync
Il comando `rsync` ha le seguenti opzioni:
- `-c` per controllare i file tramite checksum invece che per data e dimensione (che sono sempre diverse visto che stiamo ricosruendo il sito ogni volta con le GitHub Actions)
- `-a` per copiare ricorsivamente i file e mantenere i permessi
- `-v` per mostrare i file copiati
- `-z` per comprimere i file durante il trasferimento
## SSH Segreti
Per stabilire una connessione SSH a Poisson dalle GitHub Actions in modo sicuro, dobbiamo aggiungere alcuni segreti alla nostra repository. Vediamo meglio il workflow:
```yaml
- name: Write SSH keys
run: |
install -m 600 -D /dev/null ~/.ssh/known_hosts
install -m 600 -D /dev/null ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
```
Questa è la parte più importante del workflow, che permette di autenticarsi su Poisson senza dover inserire la password ogni volta (cosa che non possiamo materialmente fare dall GitHub Actions).
Per farlo, creiamo in locale una coppia di chiavi SSH apposta per le GitHub Actions e aggiungiamo la chiave pubblica su Poisson. Per farlo, possiamo seguire questi passaggi:
```bash
$ ssh-keygen -t ed25519 -C "deploy@github-actions" -f actions-deploy-key
$ ssh-copy-id -i actions-deploy-key <username>@poisson.phc.dm.unipi.it
```
Qui generiamo una chiave ssh utilizzando l'algoritmo `ed25519` (leggermente più consigliato rispetto a `rsa`, in particolare ha anche chiavi più corte), `-C` aggiunge semplicemente un commento alla chiave e `-f` specifica il file in cui salvare la chiave.
Poi eseguendo `cat actions-deploy-key` possiamo copiare il contenuto della chiave privata ed aggiungiamo il contenuto in un segreto chiamato `SSH_PRIVATE_KEY` nella nostra repository.
Poi, per evitare che la connessione venga rifiutata, eseguiamo in locale anche uno scan delle chiavi SSH di Poisson:
```bash
$ ssh-keyscan poisson.phc.dm.unipi.it
```
(se l'output è vuoto riprovare con `ssh-keyscan -4 ...`) e copiamo l'output in un segreto della nostra repository chiamato `SSH_KNOWN_HOSTS`.
Infine possiamo aggiungere anche un segreto `SSH_USER` con il nostro username o modificare anche direttamente il workflow ed inserire l'username direttamente nel file.
Ora ogni volta che facciamo un push sul branch `main` il nostro sito verrà automaticamente costruito e caricato su Poisson!
@@ -0,0 +1,82 @@
---
id: attivazione-poisson
title: Come attivare il proprio account Poisson
description: Guida per l'attivazione dell'account Poisson, con le istruzioni per il primo accesso e la configurazione del proprio spazio web
author: Luca Lombardo
tags: [poisson]
---
Poisson è un server autogestito dalla comunità studentesca di matematica, da sempre gestito con cura dai membri del PHC. Ogni studentə ha la possibilità di attivare un account personale, che consente l'accesso alla macchina via SSH e la creazione di uno spazio web personale.
## Come richiedere un account
Se non si è mai creato un account Poisson, è necessario inviare una richiesta via email a **macchinisti@lists.dm.unipi.it** includendo:
- Nome
- Cognome
- Username di ateneo (quello associato alla propria email istituzionale)
Nella mail è sufficiente specificare che si desidera attivare un account Poisson. I "macchinisti" si occuperanno di attivare l'account il prima possibile.
### Come ottenere le credenziali
Dopo l'attivazione, le credenziali del proprio account saranno disponibili accedendo al seguente sito:
<p align="center">
<a href="https://credenziali.phc.dm.unipi.it/">https://credenziali.phc.dm.unipi.it/</a>
</p>
Assicuriamoci di accedere con le credenziali di ateneo per recuperare username e password assegnati.
## Primo accesso al server
Per accedere a Poisson via SSH, è necessario:
1. Aprire un terminale (su Linux/Mac) o utilizzare un client SSH come PuTTY (su Windows).
2. Eseguire il comando:
```bash
ssh <username>@poisson.phc.dm.unipi.it
```
Dove `<username>` è il proprio username che è stato fornito con le credenziali.
## Configurazione della pagina web
Dopo aver effettuato il primo accesso, è possibile configurare la propria pagina web personale. Nella home directory del proprio account, è presente una cartella `public_html` in cui è possibile inserire i file necessari per la propria pagina web.
Vediamo un piccolo esempio di file `index.html` che possiamo creare:
```html
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sergio Steffè</title>
</head>
<body>
<h1>Sergio Steffè</h1>
<img
src="https://people.cs.dm.unipi.it/steffe/sergio.jpg"
alt="Foto di Sergio Steffè"
style="max-width: 300px; border-radius: 10px;"
/>
<p>Ciao! Sono Sergio Steffè.</p>
<p>Email: <a href="mailto:sergio.steffe@example.com">sergio.steffe@example.com</a></p>
</body>
</html>
```
Una volta salvato il file `index.html` nella cartella `public_html`, sarà possibile visualizzarlo accedendo al seguente indirizzo:
https://poisson.phc.dm.unipi.it/~<username>
Dove `<username>` è il proprio username.
### Creazione di pagine web più complesse
Per creare pagine web più complesse, suggeriamo di utilizzare il framework [Astro](https://astro.build/), che permette di creare siti web statici in modo semplice e veloce. [Abbiamo scritto una guida](/guide/pagina-poisson-con-astro) su come iniziare a utilizzare Astro per creare la nostra pagina web e caricarla su Poisson.
@@ -0,0 +1,21 @@
---
id: recupero-password
title: MI SONO SCORDATO LA PASSWORD DI POISSON COME FACCIO ORA?
description: Hai dimenticato la tua password Poisson? Niente paura, ecco cosa fare!
author: Luca Lombardo
tags: [poisson, password]
---
Se per qualche strano motivo hai dimenticato la tua password Poisson, niente paura! La procedura è super semplice:
1. Scrivi una mail a **macchinisti@lists.dm.unipi.it**.
2. Nella mail, specifica il tuo nome, cognome, username di ateneo (quello della tua email istituzionale) e se lo ricordi, anche il tuo username Poisson.
3. Attendi pazientemente la risposta dei macchinisti, che ti resetteranno la password e ti invieranno una nuova via email.
E voilà, in un batter d'occhio sarai di nuovo pronto a entrare nel fantastico mondo di Poisson.
---
**Nota:** Non preoccuparti, capita a tutti di dimenticare una password ogni tanto. Se vuoi evitare che succeda di nuovo, prova ad usare un password manager come [Bitwarden](https://bitwarden.com/) 😉
+209
View File
@@ -0,0 +1,209 @@
import Container from '../../components/meta/Container.astro'
import Palette from '../../components/meta/Palette.astro'
# Meta > Design
In questa pagina tento di spiegare come funziona il design di questo sito. I blocchi di codice con sfondo arancione chiaro sono esempi di codice Astro e sotto hanno un'anteprima del risultato _generata automaticamente_. Ad esempio
```astro
<p>Questo è un paragrafo</p>
```
Molti di questi esempi hanno alcuni stili impostati in `style` per mostrare come funzionano. Nella pratica invece è consigliato create una classe per ogni tipo di componente ed impostare le proprietà via CSS, ad esempio
```css
.my-custom-form {
--card-base: var(--palette-red);
max-width: 25rem;
}
```
```html
<div class="my-custom-form card">
<p>Questo è un paragrafo</p>
</div>
```
## Card
Le card sono uno dei componenti più importanti di questo sito. Sono utilizzate per mostrare i post, i progetti e le pagine. Ecco alcuni esempi per dare un'idea
### Esempio di base
Una card semplice ha un titolo ed una descrizione.
```astro
<div class="card" style="--card-base: var(--guide-base); max-width: 25rem;">
<div class="title">Titolo</div>
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
### Varianti
#### Grande
Le card possono essere di dimensioni diverse. Questa è una card grande.
```astro
<div class="card large" style="--card-base: lightgreen; max-width: 25rem;">
<div class="title">Titolo</div>
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
### Low Level: Mixin SCSS
Non dovrebbe essere mai necessario usarlo direttamente ma l'effetto di ombra delle card è ottenuto con questo mixin SCSS (che si trova in `src/styles/mixins.scss`).
```scss
@mixin neo-brutalist-card($size: 3px, $offset: $size + 1, $shadow: true, $hoverable: false) {
border: $size solid #222;
border-radius: $size * 2;
@if $shadow {
box-shadow: $offset $offset 0 0 #222;
}
@if $hoverable {
transition: all 64ms linear;
&:hover {
transform: translate(-1px, -1px);
box-shadow: $offset + 1 $offset + 1 0 0 #222;
}
}
}
```
Ad esempio tutti i bottoni utilizzano direttamente questo mixin senza cambiare i parametri di default.
### Sotto-componenti
#### Titolo
```astro
<div class="card">
<div class="title">Titolo</div>
</div>
```
#### Testo & Modificatori
Se c'è poco testo, può essere inserito direttamente nella card.
```astro
<div class="card">
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
Altrimenti può essere inserito in un tag `<p>`.
```astro
<div class="card">
<div class="text">
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Distinctio, vel! Veritatis est sit beatae eveniet.
</p>
<p>
Error, minus, asperiores quaerat nulla cumque, nisi ipsam
assumenda consectetur accusamus tempore consequatur quae. Fugit?
</p>
<p>
Quos sapiente amet numquam quis, libero odit eum, eius
perspiciatis repellat nesciunt cupiditate asperiores maiores?
</p>
</div>
</div>
```
C'è anche il modificatore `small` e `dimmed` per ridurre la grandezza del testo e renderlo grigio rispettivamente.
```astro
<div class="card" style="max-width: 25rem;">
<div class="text">
Some normal text, this is a very long
text that should wrap on the next line
</div>
<div class="text small">
This is some small text
</div>
<div class="text dimmed">
This is some dimmed text
</div>
<div class="text small dimmed">
This is some small dimmed text
</div>
</div>
```
#### Tags
I tag sono una lista di link con `display: flex` e `flex-wrap: wrap`.
```astro
<div class="card" style="max-width: 25rem;">
<div class="tags">
<a href="#">#tag1</a>
<a href="#">#tagg2</a>
<a href="#">#tag3</a>
<a href="#">#taggg4</a>
<a href="#">#tagg5</a>
<a href="#">#taggg6</a>
<a href="#">#tag7</a>
<a href="#">#taggg8</a>
<a href="#">#tag9</a>
<a href="#">#taggggg10</a>
</div>
</div>
```
## Palette
Varie sezioni del sito utilizzano diverse palette di colori. Questa è la palette di base.
### Guide
<Palette
colors={[
'var(--guide-base)',
'var(--guide-darkest)',
'var(--guide-darker)',
'var(--guide-dark)',
'var(--guide-light)',
'var(--guide-lighter)',
'var(--guide-lightest)',
]}
/>
## Combo Box
I combo box sono un componente per fare dropdown scritto in Preact. Questo è un esempio di come funzionano.
```js
import { ComboBox } from '@/lib/components/ComboBox'
const [value, setValue] = useState('option-1')
```
```jsx
<ComboBox value={value} setValue={setValue}>
{{
'option-1': <>Option 1</>
'option-2': <>Option 2</>
'option-3': <>Option 3</>
}}
</ComboBox>
```
+12
View File
@@ -0,0 +1,12 @@
---
# Questo documento è utilizzato nella homepage del sito nella card principale
title: Cos'è il PHC?
---
Il <span title="Pisa Happy Computing">**PHC**</span> è un laboratorio informatico, gestito dagli studenti del **Dipartimento di Matematica** di Pisa e nato nel 1999, che offre vari servizi agli studenti come [Poisson](https://poisson.phc.dm.unipi.it), che ospita le pagine degli studenti.
La sede del PHC è la [stanza 106](https://www.dm.unipi.it/mappa/?sel=638cd24b50cf34e03924a00c) del Dipartimento, dove si trovano i **macchinisti** per discutere e realizzare progetti [hardware](http://steffe.cs.dm.unipi.it/) e [software](https://lab.phc.dm.unipi.it/orario), e occuparsi di server autogestiti.
Le macchine del PHC girano principalmente Linux/Unix come sistemi operativi e i macchinisti sono grandi sostenitori di software [FOSS](https://it.wikipedia.org/wiki/Free_and_Open_Source_Software) (che loro stessi sviluppano sull'[istanza Gitea del PHC](https://git.phc.dm.unipi.it/phc)).
La lista dei vari macchinisti e di altri eventi notevoli si trova nella [pagina della storia](/storia) del PHC.
+38
View File
@@ -0,0 +1,38 @@
---
title: Il Nuovo Sito del PHC
description: Benvenuti nel nuovo sito web del PHC, realizzato in Astro. Vediamo le feature principali e cosa abbiamo in serbo per il futuro.
publishDate: 2024-10-05
---
# Il Nuovo Sito del PHC
Questo è il nuovo sito del PHC, realizzato in [Astro](https://astro.build/), un nuovo framework per realizzare siti web.
## Perché Astro?
Astro è un framework statico che ci consente di creare siti web veloci e performanti. Per ora il sito è completamente statico, ma Astro ci permetterà di passare facilmente a un modello di sito ibrido, con funzionalità dinamiche e interattive.
A differenza di altri framework, di base Astro non richiede JavaScript per funzionare, il che lo rende una scelta ideale per siti web leggeri e veloci.
## Caratteristiche del Nuovo Sito
Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti web. I contenuti del sito dei Seminarietti verranno integrati nella sezione guide che ospiterà anche molte altre risorse utili.
- **Lista Utenti**: La lista degli utenti di Poisson precedentemente presente sul sito era ordinata per nome e non era facile cercare un utente specifico. Ora la lista è ordinata in base alla data di creaizone dell'account e c'è una barra di ricerca fuzzy per cercare un utente e la sua pagina Poisson.
- **Guide**: La sezione guide ospiterà articoli, tutorial e risorse utili per gli studenti. Questa sezione sarà in costante aggiornamento con nuovi contenuti.
- **News**: La sezione news ospiterà gli novità e annunci importanti riguardanti il PHC.
- **Storia**: Questa sezione raccoglie la storia del PHC fin dalla creazione di Poisson nel lontano 1994, includendo tutti i vari macchinisti nel corso della tempo.
## Cooming Soon
- **Appunti**: Stiamo lavorando ad una nuova sezione appunti unificata che raccoglierà tutte le dispense presenti su Poisson. Gli utenti potranno cercare gli appunti per corso e per professore e scaricarli in formato PDF.
- **Account Utente**: Prossimamente sarà possibile accedere al sito del PHC sia con un account Poisson che con un account di Ateneo. Gli utenti potranno recuperare le proprie credenziali Poisson (precedentemente esisteva il sito separato https://credenziali.phc.dm.unipi.it/ per questo scopo), modificare la propria password e visualizzare i propri appunti caricati.
## Conclusioni
Speriamo che il nuovo sito vi piaccia e vi sia utile. Se avete suggerimenti o richieste, non esitate a contattarci. Buona navigazione, magari iniziando dalla [pagina sulla storia del PHC](/storia)!
View File
+2
View File
@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
+16
View File
@@ -0,0 +1,16 @@
---
import BaseLayout from './BaseLayout.astro'
import Header from '../components/Header.astro'
import Footer from '../components/Footer.astro'
---
<BaseLayout {...Astro.props}>
<Header />
<main class="article card large">
<div class="text">
<slot />
</div>
</main>
<Footer />
</BaseLayout>
+78
View File
@@ -0,0 +1,78 @@
---
import 'katex/dist/katex.min.css'
import '@fontsource/open-sans/latin.css'
import '@fontsource/source-sans-pro/latin.css'
import '@fontsource/source-code-pro/latin.css'
import '@fontsource/space-mono/latin.css'
import '@fontsource/iosevka/latin.css'
import '@fontsource-variable/material-symbols-outlined/full.css'
import '../styles/main.scss'
type Props = {
title?: string
description?: string
thumbnail?: string
/** Tags for the page, used for styling */
pageTags?: string | string[]
}
import phcIcon from '../assets/icon.png'
const { title, description, thumbnail, pageTags } = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:title" content={title ?? 'PHC'} />
<meta property="og:description" content={description ?? 'Sito web del PHC'} />
<meta name="description" content={description ?? 'Sito web del PHC'} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://phc.dm.unipi.it/" />
{thumbnail && <meta property="og:image" content={thumbnail} />}
<link rel="icon" type="image/png" sizes="512x512" href={phcIcon.src} />
<link rel="apple-touch-icon" href={phcIcon.src} />
<script>
import renderMathInElement from 'katex/contrib/auto-render'
document.addEventListener('DOMContentLoaded', function () {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true },
],
throwOnError: false,
})
})
</script>
<script is:inline>
window.goatcounter = {
path(p) {
return location.host + p
},
}
</script>
<script
is:inline
async
src="//analytics.phc.dm.unipi.it/count.js"
data-goatcounter="https://analytics.phc.dm.unipi.it/count"></script>
<title>{title ?? 'PHC'}</title>
</head>
<body class:list={typeof pageTags === 'string' ? [pageTags] : pageTags}>
<slot />
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
---
import BaseLayout from './BaseLayout.astro'
import Header from '../components/Header.astro'
import Footer from '../components/Footer.astro'
---
<BaseLayout {...Astro.props}>
<Header />
<main>
<slot />
</main>
<Footer />
</BaseLayout>
+123
View File
@@ -0,0 +1,123 @@
---
import Header from '@/components/Header.astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
---
<BaseLayout title="Admin | PHC" pageTags={'admin'}>
<Header />
<div class="layout-admin">
<div class="sidebar">
<nav>
<ul>
<li>
<a href="/admin">
<span class="material-symbols-outlined">dashboard</span>
Dashboard
</a>
</li>
<li>
<a href="/admin/utenti">
<span class="material-symbols-outlined">group</span>
Utenti
</a>
</li>
<li>
<a href="/admin/appunti">
<span class="material-symbols-outlined">book_2</span>
Appunti
</a>
</li>
<li>
<a href="/admin/database">
<span class="material-symbols-outlined">database</span>
Database
</a>
</li>
</ul>
</nav>
</div>
<div class="content">
<div class="admin-dashboard-grid max-width-content">
<div class="card">
<div class="title">
<a href="/admin/utenti">Utenti</a>
</div>
<div class="text">Statistiche degli utenti di Poisson e PHC</div>
<div class="boxes">
<div class="box">
<div class="title">Utenti Poisson</div>
<div class="material-symbols-outlined extra-large">group</div>
<div class="description">1356</div>
</div>
<div class="box">
<div class="title">Utenti PHC</div>
<div class="material-symbols-outlined extra-large">group</div>
<div class="description">345</div>
</div>
<div class="box">
<div class="title">Utenti con account collegato</div>
<div class="material-symbols-outlined extra-large">group</div>
<div class="description">56</div>
</div>
</div>
</div>
<div class="card">
<div class="title">
<a href="/admin/appunti">Appunti</a>
</div>
<div class="text">Statistiche degli Appunti</div>
<div class="boxes">
<div class="box">
<div class="title">Appunti Caricati</div>
<div class="material-symbols-outlined extra-large">book_2</div>
<div class="description">567</div>
</div>
<div class="box">
<div class="title">Appunti Pubblicati</div>
<div class="material-symbols-outlined extra-large">book_2</div>
<div class="description">345</div>
</div>
<div class="box">
<div class="title">In attesa di revisione</div>
<div class="material-symbols-outlined extra-large">book_2</div>
<div class="description">234</div>
</div>
<div class="box">
<div class="title">Spazio Usato (di Minio)</div>
<div class="material-symbols-outlined extra-large">storage</div>
<div class="description">34.5 GB</div>
</div>
</div>
</div>
<div class="card">
<div class="title">
<a href="/admin/database">Database</a>
</div>
<div class="text">Statistiche del Database</div>
<div class="boxes">
<div class="box">
<div class="title">Record nel database</div>
<div class="material-symbols-outlined extra-large">database</div>
<div class="description">3456</div>
</div>
<div class="box">
<div class="title">Record modificati</div>
<div class="material-symbols-outlined extra-large">database</div>
<div class="description">234</div>
</div>
<div class="box">
<div class="title">Record eliminati</div>
<div class="material-symbols-outlined extra-large">database</div>
<div class="description">56</div>
</div>
<div class="box">
<div class="title">Spazio utilizzato</div>
<div class="material-symbols-outlined extra-large">storage</div>
<div class="description">39.5 MB</div>
</div>
</div>
</div>
</div>
</div>
</div>
</BaseLayout>
+88
View File
@@ -0,0 +1,88 @@
---
import Header from '@/components/Header.astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
---
<BaseLayout title="Utenti | Admin | PHC" pageTags={'admin'}>
<Header />
<div class="layout-admin">
<div class="sidebar">
<nav>
<ul>
<li>
<a href="/admin">
<span class="material-symbols-outlined">dashboard</span>
Dashboard
</a>
</li>
<li>
<a href="/admin/utenti">
<span class="material-symbols-outlined">group</span>
Utenti
</a>
</li>
<li>
<a href="/admin/appunti">
<span class="material-symbols-outlined">book_2</span>
Appunti
</a>
</li>
<li>
<a href="/admin/database">
<span class="material-symbols-outlined">database</span>
Database
</a>
</li>
</ul>
</nav>
</div>
<div class="content">
<div class="admin-table max-width-content">
<div class="table-header">
<div class="table-title">
<span class="material-symbols-outlined">group</span>
Utenti
</div>
<div class="table-actions">
<a href="/admin/utenti/aggiungi" class="button">Aggiungi</a>
</div>
</div>
<div class="table-filter stack-h">
<input type="text" class="fill-w shrink" placeholder="Cerca utente" />
<button class="icon">
<div class="material-symbols-outlined">search</div>
</button>
</div>
<div class="table-body">
<div class="table-row header">
<div class="table-cell">Nome</div>
<div class="table-cell">Cognome</div>
<div class="table-cell">Email</div>
<div class="table-cell">Ruolo</div>
<div class="table-cell">Azioni</div>
</div>
{
Array.from({ length: 50 }).map(() => (
<Fragment>
<div class="table-row">
<div class="table-cell">Mario</div>
<div class="table-cell">Rossi</div>
<div class="table-cell">mario@rossi.com</div>
<div class="table-cell">studente</div>
<div class="table-cell">
<a href="/admin/utenti/modifica" class="button icon">
<div class="material-symbols-outlined">edit</div>
</a>
<a href="/admin/utenti/elimina" class="button icon">
<div class="material-symbols-outlined">delete</div>
</a>
</div>
</div>
</Fragment>
))
}
</div>
</div>
</div>
</div>
</BaseLayout>
+64
View File
@@ -0,0 +1,64 @@
---
import PageLayout from '@layouts/PageLayout.astro'
import { AppuntiList, AppuntiCard } from '@client/Appunti'
---
<PageLayout title="Appunti | PHC" pageTags="appunti">
<h1>Appunti & Dispense</h1>
<div class="search">
<input type="text" />
<span class="material-symbols-outlined">search</span>
</div>
<h2>In primo piano</h2>
<div class="appunti-scrollable center">
<AppuntiList>
<AppuntiCard client:load title="Appunti 1" author="someuser" courseYear="2023/2024" />
<AppuntiCard client:load course="Geometria 1" author="exampleuser" courseYear="2023/2024" />
<AppuntiCard client:load course="Algebra 1" author="anotheruser" courseYear="2023/2024" />
</AppuntiList>
</div>
<!-- <h2>Categorie</h2>
<h3>Analisi</h3>
<div class="appunti-scrollable">
<AppuntiList>
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
</AppuntiList>
</div>
<h3>Algebra Lineare</h3>
<div class="appunti-scrollable">
<AppuntiList>
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
</AppuntiList>
</div>
<h3>Geometria</h3>
<div class="appunti-scrollable">
<AppuntiList>
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
<AppuntiCard client:load title="Appunti 1" author="example" courseYear="2023/2024" />
</AppuntiList>
</div> -->
</PageLayout>
+41
View File
@@ -0,0 +1,41 @@
---
import { getCollection } from 'astro:content'
import ArticleLayout from '@/layouts/ArticleLayout.astro'
export async function getStaticPaths() {
const guides = await getCollection('guides')
return guides.map(entry => ({
params: { id: entry.data.id },
props: { entry },
}))
}
const { entry } = Astro.props
const { Content } = await entry.render()
---
<ArticleLayout
{...entry.data}
title={entry.data.title + ' | Guide | PHC'}
pageTags={['guida', entry.data.id, entry.data.series && 'series']}
>
<h1>{entry.data.title}</h1>
{entry.data.series && <div class="series">Serie: {entry.data.series}</div>}
<Content />
<div class="metadata">
<div class="metadata-item">
<strong>Autore</strong>
{entry.data.author}
</div>
<div class="metadata-item">
<strong>Tags</strong>
<div class="tags">
{entry.data.tags.map(tag => <a href={`/guide/tags/${tag}`}>#{tag}</a>)}
</div>
</div>
</div>
</ArticleLayout>
+30
View File
@@ -0,0 +1,30 @@
---
import { getCollection } from 'astro:content'
import PageLayout from '@layouts/PageLayout.astro'
const guides = await getCollection('guides')
---
<PageLayout title="Guide | PHC" pageTags="guide">
<h1>
<a href="/guide">Guide</a>
</h1>
<div class="card-list">
{
guides.toReversed().map(guide => (
<div class="card">
<div class="title">
<a href={`/guide/${guide.data.id}`}>{guide.data.title}</a>
</div>
<div class="text">{guide.data.description}</div>
<div class="tags">
{guide.data.tags.map((tag: string) => (
<a href={`/guide/tags/${tag}`}>#{tag}</a>
))}
</div>
</div>
))
}
</div>
</PageLayout>
+55
View File
@@ -0,0 +1,55 @@
---
import { getCollection } from 'astro:content'
import type { CollectionEntry } from 'astro:content'
import PageLayout from '@/layouts/PageLayout.astro'
export async function getStaticPaths() {
const guides = await getCollection('guides')
const tags: string[] = []
guides.forEach(post => {
post.data.tags.forEach((tag: string) => {
tags.push(tag)
})
})
return Array.from(new Set(tags)).map(tag => {
return {
params: { tag },
props: {
tag,
guides: guides.filter(post => post.data.tags.includes(tag)),
},
}
})
}
interface Props {
tag: string
guides: CollectionEntry<'guides'>[]
}
const { tag, guides } = Astro.props
---
<PageLayout title={`#${tag} | Guide | PHC`} pageTags="guide tag">
<h1><a href="/guide">Guide</a> > <a href={`/guide/tags/${tag}`}>#{tag}</a></h1>
<div class="card-list">
{
guides.map(guide => (
<div class="card">
<div class="title">
<a href={`/guide/${guide.data.id}`}>{guide.data.title}</a>
</div>
<div class="text">{guide.data.description}</div>
<div class="tags">
{guide.data.tags.map((tag: string) => (
<a href={`/guide/tags/${tag}`}>#{tag}</a>
))}
</div>
</div>
))
}
</div>
</PageLayout>
+208
View File
@@ -0,0 +1,208 @@
---
import { getCollection } from 'astro:content'
import PageLayout from '@/layouts/PageLayout.astro'
import { Content as WhatPhcContent, frontmatter as whatsPhcFrontmatter } from '@/content/meta/whats-phc.md'
import { Image } from 'astro:assets'
import ProjectCard from '@/components/ProjectCard.astro'
import HomepageZigZag from '@/components/HomepageZigZag.astro'
import Card from '@/components/Card.astro'
const news = await getCollection('news')
const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
---
<PageLayout title="PHC" pageTags="homepage">
<section class="principal">
<div class="circuit-layer">
<canvas id="circuits-art"></canvas>
<script src="../scripts/circuits-art.ts"></script>
</div>
<div class="logo">
<img src="/images/phc-logo-2024-11@x8.png" alt="phc logo" />
</div>
<div class="whats-phc card large">
<input type="checkbox" class="hide" id="mobile-whats-phc-read-more" checked />
<div class="title">{whatsPhcFrontmatter.title}</div>
<div class="text">
<WhatPhcContent />
</div>
<div class="mobile-only">
<label for="mobile-whats-phc-read-more">
<div class="button">
<span>Mostra di più</span>
<span>Mostra meno</span>
</div>
</label>
</div>
</div>
</section>
<section class="news">
<HomepageZigZag color="#C2A8EB" />
<div class="title">Ultime Notizie</div>
<div class="card-list">
{
news.map(newsItem => (
<Card>
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="text">{newsItem.data.description}</div>
</Card>
))
}
</div>
<a class="primary" href="/notizie" role="button">Vai all'Archivio</a>
</section>
<section class="projects" id="projects">
<HomepageZigZag color="#f5f2cc" />
<div class="title">Progetti</div>
<div class="project-list">
<ProjectCard
title="Gitea"
href="https://git.phc.dm.unipi.it/"
imgSrc="https://upload.wikimedia.org/wikipedia/commons/b/bb/Gitea_Logo.svg"
style="--card-bg: rgb(150, 197, 150); --masonry-height: 2;"
>
<p>Gitea è un servizio di hosting per progetti software, come GitHub ma autogestito.</p>
<p>
Qui puoi trovare i progetti del PHC, e accedendo con un account di Ateneo potrai crearne
di nuovi.
</p>
</ProjectCard>
<!-- <ProjectCard
title="PHC-Bot"
href="#"
style="--card-bg: #c55; --masonry-height: 1;"
>
<p>Un bot con cui chattare per chiedere informazioni o supporto tecnico.</p>
</ProjectCard> -->
<ProjectCard
title="Orario"
href="https://lab.phc.dm.unipi.it/orario/"
style="--card-bg: #75ca75; --masonry-height: 1;"
imgSrc="https://lab.phc.dm.unipi.it/orario/icon.png"
>
<p>
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui
docenti e le aule.
</p>
</ProjectCard>
<ProjectCard
title="Problemi"
href="https://lab.phc.dm.unipi.it/problemi/"
style="--card-bg: #aa88c0; --masonry-height: 2;"
imgSrc="https://lab.phc.dm.unipi.it/problemi/favicon/android-chrome-512x512.png"
>
<p>Bacheca di problemi di Matematica da risolvere, inviandone le soluzioni in LaTeX.</p>
<p>Di sera, puoi leggere i problemi sullo schermo del Dipartimento in atrio.</p>
</ProjectCard>
<ProjectCard
title='Cluster "Steffè"'
href="http://steffe.cs.dm.unipi.it/"
style="--masonry-height: 2;"
imgSrc="https://steffe.lb.cs.dm.unipi.it/assets/img/logo.png"
>
<p>
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo
Parallelo dall'Infrastruttura alla Matematica".
</p>
</ProjectCard>
<ProjectCard
title="Seminarietti"
href="https://seminarietti.phc.dm.unipi.it/"
style="--card-bg: #bd9fec; --masonry-height: 2;"
imgSrc="https://seminarietti.phc.dm.unipi.it/favicon.png"
>
<p>Varie conferenze del PHC su argomenti di informatica, matematica e tecnologia.</p>
</ProjectCard>
<ProjectCard
title="Tutorato"
href="https://tutorato.phc.dm.unipi.it/"
style="--card-bg: #c55; --masonry-height: 2;"
imgSrc="https://tutorato.phc.dm.unipi.it/favicon.svg"
>
<p>
Un sito con tutte le informazioni sui tutorati di Matematica, con tanto di archivio degli
anni passati.
</p>
</ProjectCard>
</div>
</section>
<section class="wanna-be-macchinista">
<HomepageZigZag color="#888" />
<div class="title"></div>
<Card large style="--card-base: #ddd;">
<div class="title" title="recentemente, anche il caffè">E cosa si fa in PHC?</div>
<div class="text">
<p>Nessuno lo sa di preciso, ma facciamo molte cose:</p>
<ul>
<li>amministrazione di sistemi Linux/Unix e macchine virtuali</li>
<li>supporto tecnico per installare e usare Linux sul proprio portatile</li>
<li>costruiamo, smontiamo ed aggiustamo computer (antichi e moderni)</li>
<li>sviluppo di software per backend e web development</li>
<li>
organizzazione di seminari di divulgazione (vedi <a href="#projects">sopra</a>)
</li>
</ul>
<p>
Inoltre, il PHC è prima di tutto un luogo dove imparare, trasmettere le proprie conoscenze
e condividere la passione per la tecnologia.
</p>
</div>
</Card>
<div class="gallery-collage">
{
galleryCollage.map((module, i) => {
const src = module.default.src
const filename = src.split('/').at(-1).split('?').at(0).split('.').at(0)
const [rows, cols] = filename.includes('@')
? filename
.split('@')
.at(-1)
.split('x')
.map((s: string) => parseInt(s))
: [1, 1]
return (
<Card style={`--rows: ${rows}; --cols: ${cols};`}>
<Image src={module.default} alt={`gallery image ${i}`} />
</Card>
)
})
}
</div>
<Card large style="--card-base: #ddd;">
<div class="title">Vuoi diventare macchinista?</div>
<div class="text">
<p>
<strong>Macchinista non si nasce, si diventa:</strong> se sei uno studente di Matematica e
vuoi diventare un macchinista, vienici a trovare!
</p>
<p>
L'unico prerequisito è la voglia di imparare! Di solito, c'è un periodo di "apprendistato"
nel quale si apprendono le basi; una volta superato potrai diventare un macchinista a
tutti gli effetti.
</p>
</div>
</Card>
</section>
</PageLayout>
+22
View File
@@ -0,0 +1,22 @@
---
import PageLayout from '../layouts/PageLayout.astro'
---
<PageLayout title="Login | PHC" pageTags="login">
<h1>Login</h1>
<!-- form with username and password, and a button for oauth login -->
<form action="/login" method="post">
<h3 class="center">Accedi con Poisson</h3>
<input type="text" id="username" placeholder="Username" name="username" required />
<input type="password" id="password" placeholder="Password" name="password" required />
<button class="primary center" type="submit">Login</button>
<hr />
<h3 class="center">Accedi con Ateneo</h3>
<a href="/auth/ateneo" class="primary center" role="button">Login</a>
</form>
<!-- <span class="material-symbols-outlined">person</span> -->
</PageLayout>
+34
View File
@@ -0,0 +1,34 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro'
import Header from '../../components/Header.astro'
import Footer from '../../components/Footer.astro'
import CodeBlockPreview from '../../components/meta/CodeBlockPreview.astro'
import { Content, getHeadings } from '../../content/meta/design.mdx'
const headings = getHeadings()
---
<BaseLayout {...Astro.props} pageTags="design">
<Header />
<aside>
<nav>
<h3>Indice</h3>
<ul>
{
headings.map(heading => (
<li style={{ '--depth': heading.depth }}>
<a href={`#${heading.slug}`}>{heading.text}</a>
</li>
))
}
</ul>
</nav>
</aside>
<main class="text">
<Content components={{ pre: CodeBlockPreview }} />
</main>
<Footer />
</BaseLayout>
+20
View File
@@ -0,0 +1,20 @@
---
import { getCollection } from 'astro:content'
import ArticleLayout from '../../layouts/ArticleLayout.astro'
export async function getStaticPaths() {
const guides = await getCollection('news')
return guides.map(entry => ({
params: { id: entry.slug },
props: { entry },
}))
}
const { entry } = Astro.props
const { Content } = await entry.render()
---
<ArticleLayout {...entry.data} title={entry.data.title + ' | Notizie | PHC'} pageTags={['notizia']}>
<Content />
</ArticleLayout>
+30
View File
@@ -0,0 +1,30 @@
---
import { getCollection } from 'astro:content'
import PageLayout from '@layouts/PageLayout.astro'
const news = await getCollection('news')
---
<PageLayout title="Notizie | PHC" pageTags="notizie">
<h1><a href="/notizie">Notizie</a></h1>
<div class="card-list">
{
news.map(newsItem => (
<div class="card">
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="text">{newsItem.data.description}</div>
</div>
))
}
</div>
</PageLayout>

Some files were not shown because too many files have changed in this diff Show More