Sección «Pedir evento» con gestor de reservas y calendario legible en móvil
- /pedir/: calendario de fechas pedidas (negro) y confirmadas (verde), findes tranqui y con ruido, solo findes por defecto con opción de ver la semana entera. Se pincha un día libre y, sin sesión, se pide entrar y se vuelve al formulario con la fecha puesta. - Gestor en Go (gestor/): cuentas por colectivo con argon2id, sesiones y CSRF, cupo y ventana de la asamblea, caducidades, registro append-only. - La cuenta de la asamblea confirma, rechaza, cambia turnos de finde y en «Todas las fechas» quita o modifica cualquier evento. - Orden «cuenta» y «clave» con -clave y -definitiva para fijar contraseñas desde el servidor; ninguna contraseña va en el repositorio. - Botón «Pedir evento» arriba de la barra lateral en todas las páginas. - Calendario semanal en móvil: lista de días con nombre y hora de cada cita. - Fuentes servidas desde fuentes/: fuera Google Fonts en todas las páginas.
This commit is contained in:
parent
c3b51be992
commit
ce3127baff
38 changed files with 4029 additions and 52 deletions
94
gestor/config.go
Normal file
94
gestor/config.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Red
|
||||
Escucha string `json:"escucha"` // siempre local: nginx hace de puerta
|
||||
BaseURL string `json:"base_url"` // https://www.laenre.net, para comprobar el Origen
|
||||
|
||||
// Rutas
|
||||
DirDatos string `json:"dir_datos"` // registro.jsonl
|
||||
DirPlantillas string `json:"dir_plantillas"` // plantillas de /pedir/
|
||||
DirEstatico string `json:"dir_estatico"` // raiz de la web, solo en modo local
|
||||
LogAuth string `json:"log_auth"` // el registro que lee fail2ban
|
||||
|
||||
// Comportamiento
|
||||
CookieSegura bool `json:"cookie_segura"` // false solo para probar sin TLS
|
||||
Local bool `json:"local"` // sirve tambien la web estatica
|
||||
MesesVista int `json:"meses_vista"` // meses que pinta /pedir/
|
||||
|
||||
Reglas Reglas `json:"reglas"`
|
||||
}
|
||||
|
||||
func ConfigPorDefecto() Config {
|
||||
return Config{
|
||||
Escucha: "127.0.0.1:8090",
|
||||
BaseURL: "https://www.laenre.net",
|
||||
DirDatos: "/var/lib/enrecal",
|
||||
DirPlantillas: "/var/www/laenre/gestor/plantillas",
|
||||
DirEstatico: "/var/www/laenre",
|
||||
LogAuth: "/var/log/enrecal/auth.log",
|
||||
CookieSegura: true,
|
||||
MesesVista: 4,
|
||||
Reglas: ReglasPorDefecto(),
|
||||
}
|
||||
}
|
||||
|
||||
func CargarConfig(ruta string) (Config, error) {
|
||||
c := ConfigPorDefecto()
|
||||
if ruta == "" {
|
||||
return c, nil
|
||||
}
|
||||
b, err := os.ReadFile(ruta)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.DisallowUnknownFields()
|
||||
if err := dec.Decode(&c); err != nil {
|
||||
return c, fmt.Errorf("%s: %w", ruta, err)
|
||||
}
|
||||
return c, c.Validar()
|
||||
}
|
||||
|
||||
func (c Config) Validar() error {
|
||||
if c.Reglas.CupoDias < 1 {
|
||||
return fmt.Errorf("cupo_dias tiene que ser 1 o mas")
|
||||
}
|
||||
if c.Reglas.MesesVentana < 1 {
|
||||
return fmt.Errorf("meses_ventana tiene que ser 1 o mas")
|
||||
}
|
||||
if c.Reglas.DiasMinimoConfirmar < 0 {
|
||||
return fmt.Errorf("dias_minimo_confirmar no puede ser negativo")
|
||||
}
|
||||
if c.Reglas.DiasMaximoPendiente < 1 {
|
||||
return fmt.Errorf("dias_maximo_pendiente tiene que ser 1 o mas")
|
||||
}
|
||||
if len(c.Reglas.DiasReservables) == 0 {
|
||||
return fmt.Errorf("dias_reservables no puede estar vacio")
|
||||
}
|
||||
if _, err := c.Reglas.AnclaFinde.Time(); err != nil {
|
||||
return fmt.Errorf("ancla_finde: %w", err)
|
||||
}
|
||||
if c.Reglas.TurnoAncla != Tranqui && c.Reglas.TurnoAncla != Ruido {
|
||||
return fmt.Errorf("turno_ancla solo puede ser %q o %q", Tranqui, Ruido)
|
||||
}
|
||||
if c.MesesVista < 1 {
|
||||
return fmt.Errorf("meses_vista tiene que ser 1 o mas")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
inactividadSesion = 8 * time.Hour
|
||||
maxIntentos = 5
|
||||
ventanaIntentos = 10 * time.Minute
|
||||
esperaBloqueo = 15 * time.Minute
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue