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
171
gestor/modelo.go
Normal file
171
gestor/modelo.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Dia es una fecha civil en formato ISO "2006-01-02". Se guarda como texto
|
||||
// porque asi se ordena y se compara igual que como se lee en el registro.
|
||||
type Dia string
|
||||
|
||||
const formatoDia = "2006-01-02"
|
||||
|
||||
func DiaDe(t time.Time) Dia { return Dia(t.Format(formatoDia)) }
|
||||
|
||||
func (d Dia) Time() (time.Time, error) {
|
||||
t, err := time.ParseInLocation(formatoDia, string(d), time.Local)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("fecha %q no válida (formato AAAA-MM-DD)", string(d))
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MustTime solo se usa sobre fechas ya validadas al entrar en el sistema.
|
||||
func (d Dia) MustTime() time.Time {
|
||||
t, err := d.Time()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (d Dia) Mas(dias int) Dia { return DiaDe(d.MustTime().AddDate(0, 0, dias)) }
|
||||
|
||||
// DiaISO devuelve 1 (lunes) a 7 (domingo).
|
||||
func (d Dia) DiaISO() int { return int(d.MustTime().Weekday()+6)%7 + 1 }
|
||||
|
||||
func (d Dia) Legible() string {
|
||||
t := d.MustTime()
|
||||
return fmt.Sprintf("%s %d de %s", diasSemana[d.DiaISO()-1], t.Day(), mesesMin[int(t.Month())-1])
|
||||
}
|
||||
|
||||
var (
|
||||
diasSemana = []string{"lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"}
|
||||
meses = []string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio",
|
||||
"Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"}
|
||||
mesesMin = []string{"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio",
|
||||
"agosto", "septiembre", "octubre", "noviembre", "diciembre"}
|
||||
)
|
||||
|
||||
// Rango de dias que ocupa un evento, ambos extremos incluidos.
|
||||
func Rango(desde, hasta Dia) []Dia {
|
||||
if hasta < desde {
|
||||
return nil
|
||||
}
|
||||
var out []Dia
|
||||
for d := desde; d <= hasta; d = d.Mas(1) {
|
||||
out = append(out, d)
|
||||
if len(out) > 400 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type Rol string
|
||||
|
||||
const (
|
||||
RolEnre Rol = "enre" // la asamblea: confirma y rechaza
|
||||
RolInterno Rol = "interno" // colectivo de la casa: solicita y amadrina
|
||||
)
|
||||
|
||||
type Cuenta struct {
|
||||
Usuario string `json:"usuario"`
|
||||
Nombre string `json:"nombre"` // nombre visible del colectivo
|
||||
Rol Rol `json:"rol"`
|
||||
Hash string `json:"hash"` // argon2id codificado
|
||||
Temporal bool `json:"temporal,omitempty"` // obliga a cambiar la clave al entrar
|
||||
Colectivo string `json:"colectivo,omitempty"` // clave en datos/horarios.json, para el color
|
||||
Activa bool `json:"activa"`
|
||||
Creada time.Time `json:"creada"`
|
||||
}
|
||||
|
||||
type Estado string
|
||||
|
||||
const (
|
||||
Solicitado Estado = "solicitado"
|
||||
Confirmado Estado = "confirmado"
|
||||
Rechazado Estado = "rechazado"
|
||||
Caducado Estado = "caducado"
|
||||
Cancelado Estado = "cancelado"
|
||||
)
|
||||
|
||||
// Ocupa indica si el estado reserva la fecha frente a otras solicitudes.
|
||||
func (e Estado) Ocupa() bool { return e == Solicitado || e == Confirmado }
|
||||
|
||||
func (e Estado) Etiqueta() string {
|
||||
switch e {
|
||||
case Solicitado:
|
||||
return "SOLICITADO"
|
||||
case Confirmado:
|
||||
return "CONFIRMADO"
|
||||
case Rechazado:
|
||||
return "RECHAZADO"
|
||||
case Caducado:
|
||||
return "CADUCADO"
|
||||
case Cancelado:
|
||||
return "CANCELADO"
|
||||
}
|
||||
return string(e)
|
||||
}
|
||||
|
||||
type Evento struct {
|
||||
ID string `json:"id"`
|
||||
Titulo string `json:"titulo"`
|
||||
Solicitante string `json:"solicitante"` // usuario de la cuenta
|
||||
Amadrinado string `json:"amadrinado,omitempty"` // colectivo externo, vacio si es propio
|
||||
Contacto string `json:"contacto,omitempty"` // contacto del externo
|
||||
Desde Dia `json:"desde"`
|
||||
Hasta Dia `json:"hasta"`
|
||||
Hora string `json:"hora,omitempty"`
|
||||
Notas string `json:"notas,omitempty"`
|
||||
Estado Estado `json:"estado"`
|
||||
Creado time.Time `json:"creado"`
|
||||
Resuelto time.Time `json:"resuelto,omitempty"`
|
||||
ResueltoPor string `json:"resuelto_por,omitempty"`
|
||||
Motivo string `json:"motivo,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Evento) Dias() []Dia { return Rango(e.Desde, e.Hasta) }
|
||||
|
||||
func (e *Evento) NumDias() int { return len(e.Dias()) }
|
||||
|
||||
// Externo indica si el evento lo organiza un colectivo de fuera amadrinado.
|
||||
func (e *Evento) Externo() bool { return e.Amadrinado != "" }
|
||||
|
||||
type Turno string
|
||||
|
||||
const (
|
||||
Tranqui Turno = "tranqui"
|
||||
Ruido Turno = "ruido"
|
||||
)
|
||||
|
||||
func (t Turno) Etiqueta() string {
|
||||
switch t {
|
||||
case Tranqui:
|
||||
return "FINDE TRANQUI"
|
||||
case Ruido:
|
||||
return "FINDE CON RUIDO"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Excepcion fija a mano el turno de un finde concreto, identificado por su viernes.
|
||||
type Excepcion struct {
|
||||
Viernes Dia `json:"viernes"`
|
||||
Turno Turno `json:"turno"`
|
||||
}
|
||||
|
||||
type Sesion struct {
|
||||
ID string
|
||||
Usuario string
|
||||
CSRF string
|
||||
Creada time.Time
|
||||
Vista time.Time
|
||||
|
||||
// Clave temporal recien generada, para ensenarla una sola vez sin que pase
|
||||
// por la URL y acabe en el historial o en el log de nginx.
|
||||
ClaveDe string
|
||||
ClaveNueva string
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue