Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9df12a8a3c | ||
|
|
c29f90ba73 | ||
|
|
59f21ebf34 | ||
|
|
9d5100e066 | ||
|
|
efd47f2a89 | ||
|
|
8a86733d5c | ||
|
|
069af4cd09 | ||
|
|
a8172f7e66 | ||
|
|
a02801c0c8 |
@@ -0,0 +1,4 @@
|
|||||||
|
Dockerfile
|
||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
*.local*
|
||||||
@@ -7,19 +7,15 @@
|
|||||||
|
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: default
|
name: default
|
||||||
type: docker
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: deploy
|
- name: deploy
|
||||||
image: node:22-alpine
|
image: node:latest
|
||||||
volumes:
|
volumes:
|
||||||
- name: host-website-dist
|
- name: host-website-dist
|
||||||
path: /mnt/website
|
path: /mnt/website
|
||||||
commands:
|
commands:
|
||||||
- uname -a
|
- npm install
|
||||||
- node -v
|
|
||||||
- npm ci
|
|
||||||
- node -e 'import Sharp from "sharp"; console.log(Sharp)'
|
|
||||||
- npm run build
|
- npm run build
|
||||||
- cp -rT ./dist /mnt/website
|
- cp -rT ./dist /mnt/website
|
||||||
|
|
||||||
@@ -36,8 +32,8 @@ trigger:
|
|||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: caddy-permissions
|
|
||||||
type: exec # this job is executed on the host machine
|
type: exec # this job is executed on the host machine
|
||||||
|
name: caddy-permissions
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- default
|
- default
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
DB_FILE_NAME=file:data.local/database.db
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
|
OAUTH_CLIENT_ID=
|
||||||
|
OAUTH_CLIENT_SECRET=
|
||||||
|
OAUTH_AUTH_URL=https://iam.unipi.it/oauth2/authorize
|
||||||
|
OAUTH_TOKEN_HOST=https://iam.unipi.it
|
||||||
|
OAUTH_TOKEN_PATH=/oauth2/token
|
||||||
|
OAUTH_REDIRECT_URL=https://phc.dm.unipi.it/api/auth/oauth2/callback/unipi
|
||||||
|
OAUTH_USER_INFO_URL=https://iam.unipi.it/oauth2/userinfo
|
||||||
|
OAUTH_SCOPES="openid profile email"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# https://dev.to/code42cate/how-to-dockerize-and-deploy-astro-6ll
|
||||||
|
|
||||||
|
# use the official Bun image
|
||||||
|
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
||||||
|
FROM oven/bun:1-alpine AS base
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
build-base \
|
||||||
|
python3
|
||||||
|
|
||||||
|
# install dependencies into temp directory to cache them and speed up future builds
|
||||||
|
FROM base AS install
|
||||||
|
RUN mkdir -p /temp/dev
|
||||||
|
COPY package.json bun.lockb /temp/dev/
|
||||||
|
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||||
|
RUN mkdir -p /temp/prod
|
||||||
|
COPY package.json bun.lockb /temp/prod/
|
||||||
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# copy node_modules from temp directory and copy (non-ignored) project files into the image
|
||||||
|
FROM base AS prerelease
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# copy production dependencies and source code into final image
|
||||||
|
FROM base AS release
|
||||||
|
COPY --from=install /temp/prod/node_modules node_modules
|
||||||
|
COPY --from=prerelease /app .
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
ENV PORT=3000
|
||||||
|
EXPOSE 3000/tcp
|
||||||
|
CMD bun run start
|
||||||
@@ -6,20 +6,25 @@ import remarkMath from 'remark-math'
|
|||||||
|
|
||||||
import yaml from '@rollup/plugin-yaml'
|
import yaml from '@rollup/plugin-yaml'
|
||||||
|
|
||||||
|
import node from '@astrojs/node'
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [yaml()],
|
plugins: [yaml()],
|
||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
},
|
},
|
||||||
|
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [remarkMath],
|
remarkPlugins: [remarkMath],
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
theme: 'github-light',
|
theme: 'github-light',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
integrations: [
|
integrations: [
|
||||||
preact({
|
preact({
|
||||||
compat: true,
|
compat: true,
|
||||||
@@ -28,5 +33,10 @@ export default defineConfig({
|
|||||||
remarkPlugins: [remarkMath],
|
remarkPlugins: [remarkMath],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
output: 'static',
|
|
||||||
|
output: 'server',
|
||||||
|
|
||||||
|
adapter: node({
|
||||||
|
mode: 'standalone',
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
website:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
container_name: website
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# https://gist.github.com/aziis98/88af12b32d9cf3eeae3929b93146fd27
|
||||||
|
# hash2addr "next.phc.dm.unipi.it"
|
||||||
|
- "127.44.207.62:1059:3000"
|
||||||
|
volumes:
|
||||||
|
- /var/lib/website:/app/data.local
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import { defineConfig } from 'drizzle-kit'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
out: './drizzle',
|
||||||
|
schema: './src/db/schema.ts',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DB_FILE_NAME!,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
CREATE TABLE `accounts` (
|
||||||
|
`user_id` text,
|
||||||
|
`provider` text NOT NULL,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `appunti` (
|
||||||
|
`user_id` text,
|
||||||
|
`hash` text NOT NULL,
|
||||||
|
`link` text,
|
||||||
|
`visibility` text NOT NULL,
|
||||||
|
`title` text NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
`tags` text NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `poisson_requests` (
|
||||||
|
`user_id` text,
|
||||||
|
`request_type` text NOT NULL,
|
||||||
|
`comments` text,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` text,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`full_name` text,
|
||||||
|
`email` text,
|
||||||
|
FOREIGN KEY (`id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `account` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`account_id` text NOT NULL,
|
||||||
|
`provider_id` text NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`access_token` text,
|
||||||
|
`refresh_token` text,
|
||||||
|
`id_token` text,
|
||||||
|
`access_token_expires_at` integer,
|
||||||
|
`refresh_token_expires_at` integer,
|
||||||
|
`scope` text,
|
||||||
|
`password` text,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `session` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`token` text NOT NULL,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL,
|
||||||
|
`ip_address` text,
|
||||||
|
`user_agent` text,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`email_verified` integer NOT NULL,
|
||||||
|
`image` text,
|
||||||
|
`created_at` integer NOT NULL,
|
||||||
|
`updated_at` integer NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `verification` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`identifier` text NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`created_at` integer,
|
||||||
|
`updated_at` integer
|
||||||
|
);
|
||||||
@@ -0,0 +1,555 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "84352768-6c24-4412-9d9e-8a3c756d31b1",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"accounts": {
|
||||||
|
"name": "accounts",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"accounts_user_id_user_id_fk": {
|
||||||
|
"name": "accounts_user_id_user_id_fk",
|
||||||
|
"tableFrom": "accounts",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"appunti": {
|
||||||
|
"name": "appunti",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"name": "hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"name": "link",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"appunti_user_id_users_id_fk": {
|
||||||
|
"name": "appunti_user_id_users_id_fk",
|
||||||
|
"tableFrom": "appunti",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"poisson_requests": {
|
||||||
|
"name": "poisson_requests",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"request_type": {
|
||||||
|
"name": "request_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"name": "comments",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"poisson_requests_user_id_users_id_fk": {
|
||||||
|
"name": "poisson_requests_user_id_users_id_fk",
|
||||||
|
"tableFrom": "poisson_requests",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"name": "full_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"users_id_user_id_fk": {
|
||||||
|
"name": "users_id_user_id_fk",
|
||||||
|
"tableFrom": "users",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider_id": {
|
||||||
|
"name": "provider_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token_expires_at": {
|
||||||
|
"name": "access_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token_expires_at": {
|
||||||
|
"name": "refresh_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_user_id_user_id_fk": {
|
||||||
|
"name": "account_user_id_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "session",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ip_address": {
|
||||||
|
"name": "ip_address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
"name": "user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_user_id_user_id_fk": {
|
||||||
|
"name": "session_user_id_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"name": "email_verified",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1749233567397,
|
||||||
|
"tag": "0000_violet_agent_zero",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,50 +5,56 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "run-s astro:sync astro:dev",
|
"dev": "run-s astro:sync astro:dev",
|
||||||
"build": "run-s astro:build",
|
"build": "run-s astro:build",
|
||||||
|
"astro:server": "bun run ./dist/server/entry.mjs",
|
||||||
|
"drizzle:migrate": "drizzle-kit migrate",
|
||||||
|
"drizzle:generate": "drizzle-kit generate",
|
||||||
|
"start": "run-s drizzle:migrate astro:server",
|
||||||
"astro:sync": "astro sync",
|
"astro:sync": "astro sync",
|
||||||
"astro:dev": "astro dev",
|
"astro:dev": "astro dev",
|
||||||
"astro:build": "astro check && astro build"
|
"astro:build": "astro check && astro build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/node": "^9.4.3",
|
"@astrojs/node": "9.0.0",
|
||||||
"@astrojs/preact": "^4.1.1",
|
"@astrojs/preact": "4.0.0",
|
||||||
"@fontsource-variable/material-symbols-outlined": "^5.2.21",
|
"@fontsource-variable/material-symbols-outlined": "^5.1.1",
|
||||||
"@fontsource/iosevka": "^5.2.5",
|
"@fontsource/iosevka": "^5.0.11",
|
||||||
"@fontsource/mononoki": "^5.2.5",
|
"@fontsource/mononoki": "^5.0.11",
|
||||||
"@fontsource/open-sans": "^5.2.6",
|
"@fontsource/open-sans": "^5.0.24",
|
||||||
"@fontsource/source-code-pro": "^5.2.6",
|
"@fontsource/source-code-pro": "^5.0.16",
|
||||||
"@fontsource/source-sans-pro": "^5.2.5",
|
"@fontsource/source-sans-pro": "^5.0.8",
|
||||||
"@fontsource/space-mono": "^5.2.8",
|
"@fontsource/space-mono": "^5.0.20",
|
||||||
|
"@libsql/client": "^0.15.8",
|
||||||
"@phosphor-icons/core": "^2.1.1",
|
"@phosphor-icons/core": "^2.1.1",
|
||||||
"@phosphor-icons/react": "^2.1.10",
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@preact/signals": "^1.3.2",
|
"@preact/signals": "^1.3.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"astro": "^5.13.7",
|
"astro": "5.1.0",
|
||||||
"fuse.js": "^7.1.0",
|
"better-auth": "^1.2.8",
|
||||||
"katex": "^0.16.22",
|
"better-sqlite3": "^11.10.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"drizzle-orm": "^0.44.2",
|
||||||
|
"drizzle-kit": "^0.31.1",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
|
"katex": "^0.16.9",
|
||||||
"lucide-static": "^0.468.0",
|
"lucide-static": "^0.468.0",
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.6",
|
||||||
"node-addon-api": "^8.5.0",
|
"preact": "^10.19.6",
|
||||||
"node-gyp": "^11.4.2",
|
"npm-run-all": "^4.1.5",
|
||||||
"preact": "^10.27.2",
|
"typescript": "^5.3.3"
|
||||||
"sharp": "^0.34.3",
|
|
||||||
"typescript": "^5.9.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/mdx": "^4.3.5",
|
"@astrojs/mdx": "4.0.2",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@types/katex": "^0.16.7",
|
"@types/katex": "^0.16.7",
|
||||||
"jsdom": "^24.1.3",
|
"jsdom": "^24.1.1",
|
||||||
"linkedom": "^0.18.12",
|
"linkedom": "^0.18.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"prettier": "^3.5.0",
|
||||||
"prettier": "^3.6.2",
|
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-toc": "^9.0.0",
|
"remark-toc": "^9.0.0",
|
||||||
"sass": "^1.92.1",
|
"tsx": "^4.7.1"
|
||||||
"tsx": "^4.20.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 877 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,28 @@
|
|||||||
|
import { betterAuth } from 'better-auth'
|
||||||
|
import { genericOAuth } from 'better-auth/plugins'
|
||||||
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||||
|
import * as authSchema from '@/db/auth-schema'
|
||||||
|
import db from '@/db'
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: drizzleAdapter(db, {
|
||||||
|
provider: 'sqlite',
|
||||||
|
schema: authSchema,
|
||||||
|
}),
|
||||||
|
plugins: [
|
||||||
|
genericOAuth({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
providerId: 'unipi',
|
||||||
|
clientId: process.env.OAUTH_CLIENT_ID!,
|
||||||
|
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
|
||||||
|
scopes: process.env.OAUTH_SCOPES!.split(' '),
|
||||||
|
userInfoUrl: process.env.OAUTH_USER_INFO_URL!,
|
||||||
|
authorizationUrl: process.env.OAUTH_AUTH_URL!,
|
||||||
|
tokenUrl: `${process.env.OAUTH_TOKEN_HOST}${process.env.OAUTH_TOKEN_PATH}`,
|
||||||
|
redirectURI: process.env.OAUTH_REDIRECT_URL!,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
@@ -41,13 +41,7 @@ export const ComboBox = ({
|
|||||||
<PhosphorIcon name="caret-down" />
|
<PhosphorIcon name="caret-down" />
|
||||||
</div>
|
</div>
|
||||||
{open && (
|
{open && (
|
||||||
<div
|
<div class={clsx('dropdown', cloak && 'invisible')} ref={el => el && setItemWidth(el.offsetWidth)}>
|
||||||
class={clsx('dropdown', cloak && 'invisible')}
|
|
||||||
ref={el => {
|
|
||||||
if (!el) return
|
|
||||||
setItemWidth(el.offsetWidth)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.keys(children).map(key => (
|
{Object.keys(children).map(key => (
|
||||||
<div
|
<div
|
||||||
class="option"
|
class="option"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
import { FunnelIcon } from '@phosphor-icons/react'
|
import { Funnel } from '@phosphor-icons/react'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
|
|
||||||
import extendedLatex from '@/client/lib/marked-latex'
|
import extendedLatex from '@/client/lib/marked-latex'
|
||||||
@@ -70,7 +70,7 @@ export const DomandeEsamiCourse = ({ course }: Props) => {
|
|||||||
{courseTags.length > 1 && (
|
{courseTags.length > 1 && (
|
||||||
<div class="card filter">
|
<div class="card filter">
|
||||||
<div class="grid-h">
|
<div class="grid-h">
|
||||||
<FunnelIcon />
|
<Funnel />
|
||||||
<strong>Filtra Tag</strong>
|
<strong>Filtra Tag</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-row-wrap">
|
<div class="flex-row-wrap">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'preact/hooks'
|
import { useState, useEffect } from 'preact/hooks'
|
||||||
|
import { render } from 'preact'
|
||||||
|
|
||||||
// Tipi per la gestione dei dati
|
// Tipi per la gestione dei dati
|
||||||
type TipoStudente = 'triennale' | 'magistrale'
|
type TipoStudente = 'triennale' | 'magistrale'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createAuthClient } from 'better-auth/client'
|
||||||
|
import { genericOAuthClient } from 'better-auth/client/plugins'
|
||||||
|
export const authClient = createAuthClient({
|
||||||
|
plugins: [genericOAuthClient()],
|
||||||
|
})
|
||||||
@@ -9,7 +9,7 @@ const extBlock = options => ({
|
|||||||
start(src) {
|
start(src) {
|
||||||
return src.match(/\$\$[^\$]/)?.index ?? -1
|
return src.match(/\$\$[^\$]/)?.index ?? -1
|
||||||
},
|
},
|
||||||
tokenizer(src, _tokens) {
|
tokenizer(src, tokens) {
|
||||||
const match = /^\$\$([^\$]+)\$\$/.exec(src)
|
const match = /^\$\$([^\$]+)\$\$/.exec(src)
|
||||||
return match ? { type: 'latex-block', raw: match[0], formula: match[1] } : undefined
|
return match ? { type: 'latex-block', raw: match[0], formula: match[1] } : undefined
|
||||||
},
|
},
|
||||||
@@ -25,7 +25,7 @@ const extInline = options => ({
|
|||||||
start(src) {
|
start(src) {
|
||||||
return src.match(/\$[^\$]/)?.index ?? -1
|
return src.match(/\$[^\$]/)?.index ?? -1
|
||||||
},
|
},
|
||||||
tokenizer(src, _tokens) {
|
tokenizer(src, tokens) {
|
||||||
const match = /^\$([^\$]+)\$/.exec(src)
|
const match = /^\$([^\$]+)\$/.exec(src)
|
||||||
return match ? { type: 'latex', raw: match[0], formula: match[1] } : undefined
|
return match ? { type: 'latex', raw: match[0], formula: match[1] } : undefined
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,17 +32,17 @@ const guidesCollection = defineCollection({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Per ora sono su un sito a parte ma prima o poi verranno migrati qui
|
// Per ora sono su un sito a parte ma prima o poi verranno migrati qui
|
||||||
// const seminariettiCollection = defineCollection({
|
const seminariettiCollection = defineCollection({
|
||||||
// type: 'content',
|
type: 'content',
|
||||||
// schema: z.object({
|
schema: z.object({
|
||||||
// title: z.string(),
|
title: z.string(),
|
||||||
// description: z.string(),
|
description: z.string(),
|
||||||
// author: z.string(),
|
author: z.string(),
|
||||||
// publishDate: z.date(),
|
publishDate: z.date(),
|
||||||
// eventDate: z.date(),
|
eventDate: z.date(),
|
||||||
// tags: z.array(z.string()),
|
tags: z.array(z.string()),
|
||||||
// }),
|
}),
|
||||||
// })
|
})
|
||||||
|
|
||||||
const metaCollection = defineCollection({
|
const metaCollection = defineCollection({
|
||||||
type: 'content',
|
type: 'content',
|
||||||
@@ -53,6 +53,6 @@ const metaCollection = defineCollection({
|
|||||||
export const collections = {
|
export const collections = {
|
||||||
news: newsCollection,
|
news: newsCollection,
|
||||||
guides: guidesCollection,
|
guides: guidesCollection,
|
||||||
// seminarietti: seminariettiCollection,
|
seminarietti: seminariettiCollection,
|
||||||
meta: metaCollection,
|
meta: metaCollection,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
---
|
|
||||||
id: stampare-via-ssh
|
|
||||||
title: Stampare via SSH
|
|
||||||
description: Istruzioni per stampare in dipartimento da remoto, tramite SSH 🖨
|
|
||||||
author: Antonio De Lucreziis, Francesco Minnocci
|
|
||||||
tags: [linux, ssh, stampanti]
|
|
||||||
---
|
|
||||||
|
|
||||||
Per stampare in dipartimento non bisogna per forza usare i computer dei laboratori, possiamo che stampare direttamente da remoto tramite SSH. Vediamo come fare!
|
|
||||||
|
|
||||||
Se non l'avete mai fatto per prima cosa bisogna poter accedere da remoto ad una macchina chiamata "login", il cui indirizzo è `login.dm.unipi.it`. Per fare l'accesso possiamo usare il seguente comando con l'account di Ateneo (non quello Poisson!)
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
ssh USERNAME_ATENEO@login.dm.unipi.it
|
|
||||||
```
|
|
||||||
|
|
||||||
Una volta connessi possiamo stampare utilizzando il comando `lpr` seguito dal nome del file che vogliamo stampare. Prima però serve trasferire il file che vogliamo stampare sulla macchina "login". Per fare ciò possiamo usare il comando `scp`: per prima cosa usciamo dalla macchina "login" (premere `Ctrl+D` oppure scrivendo `exit`), andiamo nella cartella dove si trova il file che vogliamo stampare e poi eseguiamo il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
scp NOME_FILE.pdf USERNAME_ATENEO@login.dm.unipi.it:~/Documents
|
|
||||||
```
|
|
||||||
|
|
||||||
Dove `NOME_FILE.pdf` è il nome del file che vogliamo stampare e `Documents` è un esempio di cartella dove vogliamo trasferirlo. Una volta trasferito il file possiamo rifare ssh su "login" e stampare il file con il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
lpr Documents/NOME_FILE.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternativamente possiamo stampare direttamente il file senza trasferirlo con il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
cat NOME_FILE.pdf | ssh USERNAME_ATENEO@login.dm.unipi.it lpr OPZIONI... -
|
|
||||||
```
|
|
||||||
|
|
||||||
Qui, `[OPZIONI...]` sono le opzioni che possiamo passare a `lpr` (vedi sotto). L'ultimo trattino "`-`" è molto importante e indica che il file da stampare è quello in standard input. Più precisamente, `cat NOME_FILE.pdf` invia il contenuto del file `NOME_FILE.pdf` allo standard output e `|` lo ridireziona a input di `ssh`, che a sua volta lo passa a `lpr` via rete.
|
|
||||||
|
|
||||||
## Opzioni di `lpr`
|
|
||||||
|
|
||||||
Il comando `lpr` accetta alcune opzioni che possono essere utili:
|
|
||||||
|
|
||||||
- `-P` seguito dal nome della stampante: permette di specificare la stampante su cui stampare, le stampanti disponibili in dipartimento sono
|
|
||||||
- `cdc4` che è la stampante di default e si trova in Aula 4
|
|
||||||
|
|
||||||
- `cdclf` che si trova al piano terra nel corridoio dopo l'Aula 4
|
|
||||||
|
|
||||||
- `cdc3` che si trova in Aula 3 (è un po' vecchia ma di solito funziona)
|
|
||||||
|
|
||||||
- `-#` seguito dal numero di copie: permette di specificare il numero di copie da stampare. In realtà questa opzione non funziona per vari motivi arcani e se uno passa `-#N` per stampare $N$ copie, la stampante stampa $N^2$ copie. (Questo ha scaturito una serie di ragionamenti sul modo ottimo di decomporre $N$ come somma di quadrati [con tanto di sito di comodo](https://shortest-sum-of-squares.netlify.app/)...)
|
|
||||||
|
|
||||||
- `-o sides=two-sided-long-edge`: permette di stampare **fronte-retro** (che dovrebbe essere già il default)
|
|
||||||
|
|
||||||
- `-o sides=two-sided-short-edge`: permette di stampare fronte-retro con "la rilegatura" delle pagine sul lato corto
|
|
||||||
|
|
||||||
- `-o sides=one-sided`: permette di stampare _solo fronte_, comodo per stampare i meme di laurea
|
|
||||||
|
|
||||||
- `-o fit-to-page`: permette di ridimensionare il documento per farlo entrare in un foglio (è buona prassi passare sempre questa opzione)
|
|
||||||
|
|
||||||
- `-o media=a4`: permette di specificare il formato del foglio, di default è A4 quindi non dovrebbere servire
|
|
||||||
|
|
||||||
## Altre comodità
|
|
||||||
|
|
||||||
Stampare da remoto porta anche altre comodità, ad esempio possiamo interrompere un file che abbiamo mandato in stampa per sbaglio con il comando (sempre tutti comandi da eseguire su "login")
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
cancel -a
|
|
||||||
```
|
|
||||||
|
|
||||||
> Attenzione, il comando sopra cancella tutta la propria coda di stampa, non solo l'ultimo lavoro inviato.
|
|
||||||
|
|
||||||
Alternativamente possiamo vedere lo stato della coda di stampa con il comando
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
lpq -a
|
|
||||||
```
|
|
||||||
|
|
||||||
e cancellare un lavoro con uno specifico ID con
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
cancel ID
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Calcola la tua media ed il voto di laurea con il nuovissimo calcolatore del PHC!
|
title: Calcola la tua media ed il voto di laurea con il nuovissimo calcolatore del PHC!
|
||||||
description: È ora disponibile uno strumento per calcolare la propria media pesata e il voto di ammissione alla laurea secondo le regole del dipartimento.
|
description: È ora disponibile uno strumento per calcolare la propria media pesata e il voto di ammissione alla laurea secondo le regole del dipartimento
|
||||||
publishDate: 2025-06-26
|
publishDate: 2025-06-26
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
title: Esplora i meme dell'aula studenti online!
|
|
||||||
description: |
|
|
||||||
Gli storici meme sono stati staccati per i lavori, ma non disperare: li potrai vedere su una nuova pagina.
|
|
||||||
publishDate: 2025-06-26
|
|
||||||
---
|
|
||||||
|
|
||||||
# Esplora i meme dell'aula studenti online!
|
|
||||||
|
|
||||||
Visti gli imminenti lavori che occuperanno l'aula studenti, ad inizio Settembre tutti i meme sulle pareti sono stati staccati e riposti temporaneamente in PHC; qui sotto trovate alcuni timelapse della giornata:
|
|
||||||
|
|
||||||
<div class="grid-h-split">
|
|
||||||
<video controls>
|
|
||||||
<source src="https://static.phc.dm.unipi.it/timelapse-nord.mp4" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
<video controls>
|
|
||||||
<source src="https://static.phc.dm.unipi.it/timelapse-sud.mp4" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
<video controls>
|
|
||||||
<source src="https://static.phc.dm.unipi.it/timelapse-termosifone.mp4" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Per questo motivo, li abbiamo scansionati ed abbiamo creato una bacheca per poter contemplare i meme dovunque voi siate 🧳
|
|
||||||
|
|
||||||
Eccovi dunque il link alla pagina, buon divertimento:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://meme.phc.dm.unipi.it">meme.phc.dm.unipi.it</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Coming Soon
|
|
||||||
|
|
||||||
Prima o poi faremo anche una mappa interattiva della stanza, basata sul seguente modello 3D ricostruito con tecniche di fotogrammetria:
|
|
||||||
|
|
||||||
<video controls>
|
|
||||||
<source src="https://static.phc.dm.unipi.it/3d-scan-preview.mp4" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
|
|
||||||
> Disclaimer: Se volessi rimuovere una tua immagine da questa pagina, scrivici pure a <a href="mailto:macchinisti@lists.dm.unipi.it">macchinisti@lists.dm.unipi.it</a> e ce ne occuperemo.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
title: Non avete attivato la 2FA entro il primo dicembre? Ecco come recuperare l'accesso
|
|
||||||
description: |
|
|
||||||
Se non avete attivato l'autenticazione a due fattori entro il primo dicembre, siete bloccati fuori dall'account Unipi. La procedura di recupero passa dal supporto tecnico.
|
|
||||||
publishDate: 2025-12-05
|
|
||||||
---
|
|
||||||
|
|
||||||
# Non avete attivato la 2FA entro il primo dicembre? Ecco come recuperare l'accesso
|
|
||||||
|
|
||||||
Se siamo rimasti fuori dal nostro account Unipi perché non abbiamo attivato l'autenticazione a due fattori entro il primo dicembre, il percorso di recupero è piuttosto lineare.
|
|
||||||
|
|
||||||
Il primo passo è mandare una mail a
|
|
||||||
|
|
||||||
> <a href="mailto:help.polo2@ticket.unipi.it">help.polo2@ticket.unipi.it</a>
|
|
||||||
|
|
||||||
usando un indirizzo email personale (quello di ateneo è bloccato). Nel messaggio dobbiamo specificare chiaramente il problema e includere il nostro indirizzo email d'ateneo.
|
|
||||||
|
|
||||||
Dopo aver inviato la richiesta, tocca aspettare. Il supporto tecnico resetta manualmente lo status 2FA e ci manda una conferma. A quel punto possiamo procedere con l'attivazione seguendo le istruzioni ufficiali:
|
|
||||||
|
|
||||||
> https://it.unipi.it/configurazioni/mfa/autenticazione-a-piu-fattori-mfa-microsoft-365/
|
|
||||||
|
|
||||||
**Nota tecnica:** L'app Microsoft Authenticator non è obbligatoria! Qualsiasi autenticatore compatibile con [TOTP](https://en.wikipedia.org/wiki/Time-based_one-time_password) va bene: Google Authenticator, Bitwarden, Authy e altre alternative funzionano perfettamente. L'unica differenza è che l'app Microsoft Authenticator permette di ricevere notifiche l'accesso che consente di fare login con meno click.
|
|
||||||
|
|
||||||
Per utilizzare una di queste altre app (invece di Microsoft Authenticator), seguire questi step nella creazione del metodo di autenticazione:
|
|
||||||
|
|
||||||
1. Selezionare **"Microsoft Authenticator"**
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2. Selezionare **"Configura un'app di autenticazione diversa"**
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
3. A questo punto il procedimento cambia in base all'app. In generale verrà richiesto di scannerizzare un QR code e di verificare il corretto funzionamento inserendo la TOTP (codice a 6 cifre)
|
|
||||||
@@ -1117,400 +1117,6 @@ questions:
|
|||||||
tags:
|
tags:
|
||||||
- 2023
|
- 2023
|
||||||
|
|
||||||
# Raccolta di Istituzioni di Geometria da Valeria
|
|
||||||
|
|
||||||
# Raccolta di Istituzioni di Geometria da Fra
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Intorno tubolare, esistenza
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Gruppi di Lie, esiste un unico sottogruppo connesso con data sottoalgebra
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Frobenius
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Invarianza omotopica di de Rham
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Mayer-Vietoris
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Cartan-Hadamard
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Lemma di Gauss
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Whitney
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Localmente euclideo se e solo se R=0
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Mayer-Vietoris a supporto compatto
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Orientabilità di RP^n e CP^n
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Lemma di Poincaré (dimostrare anche che mappe omotope inducono stesso pullback sulla coomologia), brevissimo accenno al caso a supporto compatto (nessuna dimostrazione)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Cos'è il trasporto parallelo (tutti i dettagli di buona definizione a partire dalla definizione di campo parallelo)? Se metto una metrica cosa succede? (Statement delle condizioni equivalenti di compatibilità senza dimostrazione)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Dimostra che l'algebra di Lie associata ad un gruppo di Lie è un'algebra di Lie in senso astratto (voleva giusto sentirsi dire che il bracket di campi invarianti a sinistra è invariante a sinistra senza dimostrazione)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema di corrispondenza algebre di lie
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Quando gli spazi proiettivi reali sono orientabili
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Esistenza dell'intorno tubolare (senza dimostrazione degli "esercizi")
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Dualità di Poincaré 2
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definizione di connessione metrica e condizioni equivalenti (solo enunciato)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Hopf-Rinow
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Isotopia Ambiente
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Differenziale Esterno
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Stokes
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Raddrizzamento simultaneo
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definizione di fibrato tangente
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definizione di intorno tubolare e esistenza
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema di Frobenius
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Piatto se e solo se localmente isometrico
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Quando lo spazio proiettivo reale è orientabile? E quello complesso?
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Isotopia e isotopia ambiente
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Differenziale k-forme
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Corrispondenza tra sottogruppi di lie connessi e sottoalgebre di lie
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Torsione di una connessione
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Dualità di poincarè (perché la mappa DP passa in coomologia?)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Parlare dell'algebra di Lie associata a un Gruppo di Lie
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Enunciato di Frobenius, e perché una distribuzione è integrabile sse è loc. costante
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Poincaré Dualità
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Conseguenze (Betti numeri, specchiabilità)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Distribuzioni, esempi di distribuzioni che (non) sono embeddings
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Trasporto parallelo e geodetiche
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Esempio di varietà non geodeticamente completa, si può avere compatto? (Hopf rinow)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Esistenza di forma volume per varietà orientate e di struttura riemanniana sui fibrati
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definizione di foliazione
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Localmente euclidea sse piatto
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema di Stokes
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema di Hopf-Rinow
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Orientabilità di CP^n
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definizione di forma volume ed esistenza
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Stokes (dim.)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
isotopia e isotopia ambiente (def. + teo.)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
tensore Riemann (def.)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
teo. sottoalgebre di Lie
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
esistenza metrica Lorentziana e Riemanniana
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
derivata di k-forme
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
lemma Poincaré e teo. sull'omotopia
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Cos'è l'algebra di Lie (molto veloce)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Cos'è la connessione di Levi Civita ed elencare le forme equivalenti di "compatibilità con la metrica"
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Definire la torsione e fare i conti per mostrare che è un tensore
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema di Whitney (caso compatto e caso generale).
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
M ammette struttura lorentziana orientabile temporalmente sse pettinabile.
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Teorema su sottoalgebre di Lie
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
geodetiche (praticamente solo quale è l'equazione e perché ha senso fare la nabla di un campo su una curva)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Frobenius, enunciato e dimostrazione (dando per scontate le def.equivalenti di foliazione)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
Whitney (entrambi)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
|
||||||
content: |
|
|
||||||
quando il proiettivo è orientabile (ed enunciato del teorema da cui segue)
|
|
||||||
tags:
|
|
||||||
- 2025
|
|
||||||
|
|
||||||
# Raccolta di Istituzioni di Geometria da Fra
|
# Raccolta di Istituzioni di Geometria da Fra
|
||||||
|
|
||||||
- course: istituzioni-di-geometria
|
- course: istituzioni-di-geometria
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
|
||||||
|
|
||||||
|
export const user = sqliteTable('user', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
emailVerified: integer('email_verified', { mode: 'boolean' })
|
||||||
|
.$defaultFn(() => false)
|
||||||
|
.notNull(),
|
||||||
|
image: text('image'),
|
||||||
|
createdAt: integer('created_at', { mode: 'timestamp' })
|
||||||
|
.$defaultFn(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
||||||
|
.$defaultFn(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const session = sqliteTable('session', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
token: text('token').notNull().unique(),
|
||||||
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
ipAddress: text('ip_address'),
|
||||||
|
userAgent: text('user_agent'),
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const account = sqliteTable('account', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
accountId: text('account_id').notNull(),
|
||||||
|
providerId: text('provider_id').notNull(),
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
|
accessToken: text('access_token'),
|
||||||
|
refreshToken: text('refresh_token'),
|
||||||
|
idToken: text('id_token'),
|
||||||
|
accessTokenExpiresAt: integer('access_token_expires_at', { mode: 'timestamp' }),
|
||||||
|
refreshTokenExpiresAt: integer('refresh_token_expires_at', { mode: 'timestamp' }),
|
||||||
|
scope: text('scope'),
|
||||||
|
password: text('password'),
|
||||||
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const verification = sqliteTable('verification', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
identifier: text('identifier').notNull(),
|
||||||
|
value: text('value').notNull(),
|
||||||
|
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => /* @__PURE__ */ new Date()),
|
||||||
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).$defaultFn(() => /* @__PURE__ */ new Date()),
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import { drizzle } from 'drizzle-orm/libsql'
|
||||||
|
|
||||||
|
export default drizzle(process.env.DB_FILE_NAME!)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
|
export * from './auth-schema'
|
||||||
|
|
||||||
|
import { user } from './auth-schema'
|
||||||
|
|
||||||
|
export const usersTable = sqliteTable('users', {
|
||||||
|
id: text('id').references(() => user.id),
|
||||||
|
username: text('username').notNull(),
|
||||||
|
fullName: text('full_name'),
|
||||||
|
email: text('email'),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const accountsTable = sqliteTable('accounts', {
|
||||||
|
userId: text('user_id').references(() => user.id),
|
||||||
|
provider: text('provider').notNull(),
|
||||||
|
username: text('username').notNull(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const poissonRequestsTable = sqliteTable('poisson_requests', {
|
||||||
|
userId: text('user_id').references(() => usersTable.id),
|
||||||
|
requestType: text('request_type').notNull(), // "link" or "create"
|
||||||
|
comments: text('comments'), // contains username suggestions
|
||||||
|
})
|
||||||
|
|
||||||
|
export const appuntiTable = sqliteTable('appunti', {
|
||||||
|
userId: text('user_id').references(() => usersTable.id),
|
||||||
|
hash: text('hash').notNull(),
|
||||||
|
link: text('link'),
|
||||||
|
visibility: text('visibility').notNull(), // "public", "internal", "private"
|
||||||
|
title: text('title').notNull(),
|
||||||
|
description: text('description'),
|
||||||
|
tags: text('tags').notNull(), // Array of strings, e.g. ["#geometria-2", "#511aa", "#2017-18", "#frigerio"]
|
||||||
|
})
|
||||||
@@ -1,2 +1,10 @@
|
|||||||
/// <reference path="../.astro/types.d.ts" />
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
||||||
|
declare namespace App {
|
||||||
|
// Note: 'import {} from ""' syntax does not work in .d.ts files.
|
||||||
|
interface Locals {
|
||||||
|
user: import('better-auth').User | null
|
||||||
|
session: import('better-auth').Session | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { auth } from '@/auth'
|
||||||
|
import { defineMiddleware } from 'astro:middleware'
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware(async (context, next) => {
|
||||||
|
const isAuthed = await auth.api.getSession({
|
||||||
|
headers: context.request.headers,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isAuthed) {
|
||||||
|
context.locals.user = isAuthed.user
|
||||||
|
context.locals.session = isAuthed.session
|
||||||
|
} else {
|
||||||
|
context.locals.user = null
|
||||||
|
context.locals.session = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { auth } from '@/auth'
|
||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
|
||||||
|
export const ALL: APIRoute = async ctx => {
|
||||||
|
// If you want to use rate limiting, make sure to set the 'x-forwarded-for' header to the request headers from the context
|
||||||
|
// ctx.request.headers.set("x-forwarded-for", ctx.clientAddress);
|
||||||
|
return auth.handler(ctx.request)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
import { authClient } from '@/client/auth-client' //import the auth client
|
||||||
|
|
||||||
|
await authClient.signIn.oauth2({
|
||||||
|
providerId: 'unipi',
|
||||||
|
callbackURL: '/profilo', // the path to redirect to after the user is authenticated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -11,11 +11,7 @@ import Card from '@/components/Card.astro'
|
|||||||
|
|
||||||
const news = await getCollection('news')
|
const news = await getCollection('news')
|
||||||
|
|
||||||
// const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
||||||
|
|
||||||
const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|
||||||
import.meta.glob('@/assets/gallery/*.jpg', { eager: true }),
|
|
||||||
)
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageLayout title="PHC" pageTags="homepage">
|
<PageLayout title="PHC" pageTags="homepage">
|
||||||
@@ -51,9 +47,7 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
|
|
||||||
<div class="card-list">
|
<div class="card-list">
|
||||||
{
|
{
|
||||||
news
|
news.toReversed().map(newsItem => (
|
||||||
.sort((s, t) => -s.id.localeCompare(t.id))
|
|
||||||
.map(newsItem => (
|
|
||||||
<Card>
|
<Card>
|
||||||
<a href={`/notizie/${newsItem.slug}`} class="title">
|
<a href={`/notizie/${newsItem.slug}`} class="title">
|
||||||
{newsItem.data.title}
|
{newsItem.data.title}
|
||||||
@@ -100,9 +94,9 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
</ProjectCard> -->
|
</ProjectCard> -->
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title="Orario"
|
title="Orario"
|
||||||
href="https://orario.phc.dm.unipi.it/"
|
href="https://lab.phc.dm.unipi.it/orario/"
|
||||||
style="--card-bg: #75ca75; --masonry-height: 1;"
|
style="--card-bg: #75ca75; --masonry-height: 1;"
|
||||||
imgSrc="https://orario.phc.dm.unipi.it/icon.png"
|
imgSrc="https://lab.phc.dm.unipi.it/orario/icon.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui docenti e
|
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui docenti e
|
||||||
@@ -111,9 +105,9 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title="Problemi"
|
title="Problemi"
|
||||||
href="https://problemi.phc.dm.unipi.it/"
|
href="https://lab.phc.dm.unipi.it/problemi/"
|
||||||
style="--card-bg: #aa88c0; --masonry-height: 2;"
|
style="--card-bg: #aa88c0; --masonry-height: 2;"
|
||||||
imgSrc="https://problemi.phc.dm.unipi.it/favicon/android-chrome-512x512.png"
|
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>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>
|
<p>Di sera, puoi leggere i problemi sullo schermo del Dipartimento in atrio.</p>
|
||||||
@@ -122,7 +116,7 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
title='Cluster "Steffè"'
|
title='Cluster "Steffè"'
|
||||||
href="http://steffe.cs.dm.unipi.it/"
|
href="http://steffe.cs.dm.unipi.it/"
|
||||||
style="--masonry-height: 2;"
|
style="--masonry-height: 2;"
|
||||||
imgSrc="/images/steffe-logo.png"
|
imgSrc="https://steffe.lb.cs.dm.unipi.it/assets/img/logo.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo Parallelo
|
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo Parallelo
|
||||||
@@ -152,7 +146,7 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
title="GGWP"
|
title="GGWP"
|
||||||
href="https://ggwp.phc.dm.unipi.it/"
|
href="https://ggwp.phc.dm.unipi.it/"
|
||||||
style="--card-bg: rgb(255, 157, 65); --masonry-height: 2;"
|
style="--card-bg: rgb(255, 157, 65); --masonry-height: 2;"
|
||||||
imgSrc="/images/ggwp-icon.png"
|
imgSrc="https://ggwp.phc.dm.unipi.it/ggwp-icon.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli organizzatori per
|
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli organizzatori per
|
||||||
@@ -163,7 +157,7 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
title="Chat"
|
title="Chat"
|
||||||
href="https://chat.phc.dm.unipi.it/"
|
href="https://chat.phc.dm.unipi.it/"
|
||||||
style="--card-bg: #383838; --card-fg: #ddd; --masonry-height: 1;"
|
style="--card-bg: #383838; --card-fg: #ddd; --masonry-height: 1;"
|
||||||
imgSrc="/images/chat-favicon.png"
|
imgSrc="https://chat.phc.dm.unipi.it/favicon.png"
|
||||||
>
|
>
|
||||||
<p>Istanza di Open-WebUI, una chat stile ChatGPT con alcuni LLM self-hostati dal PHC.</p>
|
<p>Istanza di Open-WebUI, una chat stile ChatGPT con alcuni LLM self-hostati dal PHC.</p>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
@@ -204,16 +198,14 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
|
|||||||
{
|
{
|
||||||
galleryCollage.map((module, i) => {
|
galleryCollage.map((module, i) => {
|
||||||
const src = module.default.src
|
const src = module.default.src
|
||||||
const filename = src.split('/').at(-1)?.split('?').at(0)?.split('.').at(0)
|
const filename = src.split('/').at(-1).split('?').at(0).split('.').at(0)
|
||||||
|
|
||||||
if (!filename) return null
|
|
||||||
|
|
||||||
const [rows, cols] = filename.includes('@')
|
const [rows, cols] = filename.includes('@')
|
||||||
? (filename
|
? filename
|
||||||
.split('@')
|
.split('@')
|
||||||
.at(-1)
|
.at(-1)
|
||||||
?.split('x')
|
.split('x')
|
||||||
.map((s: string) => parseInt(s)) ?? [1, 1])
|
.map((s: string) => parseInt(s))
|
||||||
: [1, 1]
|
: [1, 1]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import PageLayout from '../layouts/PageLayout.astro'
|
import PageLayout from '../layouts/PageLayout.astro'
|
||||||
|
import '@/styles/pages/login.css'
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageLayout title="Login | PHC" pageTags="login">
|
<PageLayout title="Login | PHC" pageTags="login">
|
||||||
@@ -7,16 +8,25 @@ import PageLayout from '../layouts/PageLayout.astro'
|
|||||||
|
|
||||||
<!-- form with username and password, and a button for oauth login -->
|
<!-- form with username and password, and a button for oauth login -->
|
||||||
<form action="/login" method="post">
|
<form action="/login" method="post">
|
||||||
<h3 class="center">Accedi con Poisson</h3>
|
<h3 class="center"><strike>Accedi con Poisson</strike> (Work in Progress)</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>
|
<div class="row">
|
||||||
|
<span class="material-symbols-outlined">person</span>
|
||||||
|
<input type="text" id="username" placeholder="Username" name="username" required disabled />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="material-symbols-outlined">key</span>
|
||||||
|
<input type="password" id="password" placeholder="Password" name="password" required disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="primary center" type="submit" disabled>Login</button>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h3 class="center">Accedi con Ateneo</h3>
|
<h3 class="center">Accedi con Ateneo</h3>
|
||||||
<a href="/auth/ateneo" class="primary center" role="button">Login</a>
|
<a href="/auth/ateneo" class="primary center" role="button">
|
||||||
|
<span class="material-symbols-outlined">account_balance</span>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
</form>
|
</form>
|
||||||
<!-- <span class="material-symbols-outlined">person</span> -->
|
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ const news = await getCollection('news')
|
|||||||
<h1><a href="/notizie">Notizie</a></h1>
|
<h1><a href="/notizie">Notizie</a></h1>
|
||||||
<div class="card-list">
|
<div class="card-list">
|
||||||
{
|
{
|
||||||
news
|
news.toReversed().map(newsItem => (
|
||||||
.sort((s, t) => -s.id.localeCompare(t.id))
|
|
||||||
.map(newsItem => (
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<a href={`/notizie/${newsItem.slug}`} class="title">
|
<a href={`/notizie/${newsItem.slug}`} class="title">
|
||||||
{newsItem.data.title}
|
{newsItem.data.title}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from '../layouts/PageLayout.astro'
|
||||||
|
import '@/styles/pages/login.css'
|
||||||
|
|
||||||
|
if (!Astro.locals.user) {
|
||||||
|
return Astro.redirect('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, email } = Astro.locals.user
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout title="Profilo | PHC">
|
||||||
|
<div class="card large">
|
||||||
|
<div class="title">Profilo di {name}</div>
|
||||||
|
<div class="text">
|
||||||
|
<p>
|
||||||
|
<strong>Nome:</strong>
|
||||||
|
{name}
|
||||||
|
<br />
|
||||||
|
<strong>Email:</strong>
|
||||||
|
{email}
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Benvenuto nel tuo profilo! Qui puoi visualizzare le tue informazioni personali e modificare le tue
|
||||||
|
impostazioni.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="title">Impostazioni</div>
|
||||||
|
<div class="text">
|
||||||
|
<p>
|
||||||
|
Al momento non sono disponibili impostazioni modificabili. Se hai bisogno di assistenza, contatta il
|
||||||
|
supporto tecnico.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageLayout>
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/* This file is here for historical reasons but is not used anymore */
|
|
||||||
|
|
||||||
@layer page {
|
|
||||||
/*
|
|
||||||
.login {
|
|
||||||
background: #ddfaff;
|
|
||||||
|
|
||||||
main {
|
|
||||||
justify-self: center;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
max-width: 80ch;
|
|
||||||
padding: 3rem 0;
|
|
||||||
|
|
||||||
gap: 3rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1042,20 +1042,6 @@ $news-accent-bg: #f8e8b1;
|
|||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-h-split {
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
place-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
grid-auto-columns: 1fr;
|
|
||||||
|
|
||||||
@media screen and (max-width: 1024px) {
|
|
||||||
grid-auto-flow: row;
|
|
||||||
grid-auto-columns: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ Controls - for things like buttons, input, select
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translate(-1px, -1px);
|
transform: translate(-1px, -1px);
|
||||||
@@ -206,6 +208,23 @@ Controls - for things like buttons, input, select
|
|||||||
background: #4ea2b1;
|
background: #4ea2b1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: #aaa;
|
||||||
|
color: #666;
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 4px 4px 0 0 #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #aaa;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 4px 4px 0 0 #222;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@@ -217,6 +236,25 @@ Controls - for things like buttons, input, select
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
span.material-symbols-outlined {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'],
|
||||||
|
input[type='password'] {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
}
|
}
|
||||||
@@ -226,6 +264,12 @@ Controls - for things like buttons, input, select
|
|||||||
.center {
|
.center {
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
details {
|
details {
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
min-height: calc(100vh - 10rem);
|
min-height: calc(100vh - 10rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center; */
|
||||||
|
|
||||||
& {
|
& {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
@@ -164,10 +168,6 @@
|
|||||||
color: #000d;
|
color: #000d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
|
||||||
grid-template-rows: auto auto 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section.projects {
|
section.projects {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
@layer page {
|
||||||
|
main {
|
||||||
|
justify-self: center;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
padding: 4.5rem 3rem;
|
||||||
|
gap: 4.5rem;
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,8 +146,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img,
|
img {
|
||||||
video {
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
@@ -157,7 +156,7 @@
|
|||||||
border: 2px solid #333;
|
border: 2px solid #333;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
box-shadow: 0.25rem 0.25rem 0 0 #333;
|
box-shadow: 0.25rem 0.25rem 0 0 #333;
|
||||||
background: #000;
|
/* background: color-mix(in lab, var(--card-base-internal, #ededed), #fff 20%); */
|
||||||
|
|
||||||
&.fill {
|
&.fill {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -174,10 +173,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
video {
|
|
||||||
margin: 1rem auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:first-child {
|
p:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|||||||