- Rol «externas» para la asamblea de externas: pide sin cupo, siempre con el colectivo de fuera y su contacto, y sus solicitudes salen en naranja hasta que la asamblea de la Enre las confirma. - /pedir/ explica en una línea que los colectivos externos piden fecha en la asamblea de externas, los lunes de 19:00 a 20:00. - Seguridad: argon2 limitado a dos cálculos a la vez y el intento se cuenta antes de calcular (60 accesos simultáneos llevaban el proceso a 3,8 GB); bloqueo por cuenta a 20 fallos para que no se pueda dejar fuera a la asamblea; avisos por sesión en vez de por URL; sig sin «..»; eventos de 31 días como mucho; parámetros de argon2 acotados. - Títulos pegados arriba en todas las páginas; la franja de la enredadera recupera ese alto para seguir llenando la pantalla. - despliegue/: instalar.sh, cuenta.sh, servicio de systemd con el proceso encerrado, fragmento de nginx que además deja de servir .git y las notas internas, y configuración de ejemplo. Ninguna contraseña en el repositorio.
168 lines
4 KiB
Go
168 lines
4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// FilaEvento es un evento ya resuelto para pintarlo en una celda del mes.
|
|
type FilaEvento struct {
|
|
Hora string
|
|
Texto string
|
|
Quien string
|
|
Estado Estado
|
|
Externas bool // la pidió la asamblea de externas
|
|
Etiqueta string
|
|
Sigue bool // dia intermedio o final de un evento de varios dias
|
|
}
|
|
|
|
type Celda struct {
|
|
Dia Dia
|
|
Numero int
|
|
Vacia bool
|
|
Hoy bool
|
|
Pasado bool
|
|
Libre bool // se puede pedir: dia reservable, dentro de la ventana y sin ocupar
|
|
Turno Turno
|
|
EtiquetaTurno string // solo en el viernes, para no repetirla tres veces
|
|
Eventos []FilaEvento
|
|
}
|
|
|
|
type Semana struct {
|
|
Dias []Celda
|
|
}
|
|
|
|
type Mes struct {
|
|
Nombre string
|
|
Anio int
|
|
Semanas []Semana
|
|
}
|
|
|
|
// Vista arma los meses que se pintan, a partir del estado del registro.
|
|
// Aqui no hay horarios semanales: esos siguen en calendario.html, que se dibuja
|
|
// con su propio guion y no se toca.
|
|
type Vista struct {
|
|
roles map[string]Rol
|
|
reglas Reglas
|
|
exc map[Dia]Turno
|
|
eventos []Evento
|
|
nombres map[string]string
|
|
hoy Dia
|
|
}
|
|
|
|
func (v Vista) eventosDe(d Dia) []FilaEvento {
|
|
var out []FilaEvento
|
|
for _, e := range v.eventos {
|
|
if !e.Estado.Ocupa() || d < e.Desde || d > e.Hasta {
|
|
continue
|
|
}
|
|
quien := v.nombres[e.Solicitante]
|
|
if quien == "" {
|
|
quien = e.Solicitante
|
|
}
|
|
externas := v.roles[e.Solicitante] == RolExternas
|
|
switch {
|
|
case externas:
|
|
quien = e.Amadrinado + " · asamblea de externas"
|
|
case e.Externo():
|
|
quien = fmt.Sprintf("%s · amadrina %s", e.Amadrinado, quien)
|
|
}
|
|
out = append(out, FilaEvento{
|
|
Hora: e.Hora,
|
|
Texto: e.Titulo,
|
|
Quien: quien,
|
|
Estado: e.Estado,
|
|
Externas: externas,
|
|
Etiqueta: etiquetaEstado(e.Estado, externas),
|
|
Sigue: d != e.Desde,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (v Vista) celda(d Dia, numero int) Celda {
|
|
turno := v.reglas.TurnoDe(d, v.exc)
|
|
etiqueta := ""
|
|
if turno != "" && d.DiaISO() == 5 {
|
|
etiqueta = turno.Etiqueta()
|
|
}
|
|
primero, ultimo := v.reglas.Ventana(v.hoy)
|
|
eventos := v.eventosDe(d)
|
|
return Celda{
|
|
Dia: d,
|
|
Numero: numero,
|
|
Hoy: d == v.hoy,
|
|
Pasado: d < v.hoy,
|
|
Libre: len(eventos) == 0 && d >= primero && d <= ultimo && v.reglas.reservable(d),
|
|
Turno: turno,
|
|
EtiquetaTurno: etiqueta,
|
|
Eventos: eventos,
|
|
}
|
|
}
|
|
|
|
func (v Vista) Mes(anio, mes int) Mes {
|
|
primero := fechaMes(anio, mes)
|
|
total := diasDelMes(anio, mes)
|
|
hueco := (int(primero.Weekday()) + 6) % 7 // celdas vacias antes del dia 1
|
|
|
|
m := Mes{Nombre: meses[mes-1], Anio: anio}
|
|
semana := Semana{}
|
|
for i := 0; i < hueco; i++ {
|
|
semana.Dias = append(semana.Dias, Celda{Vacia: true})
|
|
}
|
|
for dia := 1; dia <= total; dia++ {
|
|
d := DiaDe(primero.AddDate(0, 0, dia-1))
|
|
semana.Dias = append(semana.Dias, v.celda(d, dia))
|
|
if len(semana.Dias) == 7 {
|
|
m.Semanas = append(m.Semanas, semana)
|
|
semana = Semana{}
|
|
}
|
|
}
|
|
if len(semana.Dias) > 0 {
|
|
for len(semana.Dias) < 7 {
|
|
semana.Dias = append(semana.Dias, Celda{Vacia: true})
|
|
}
|
|
m.Semanas = append(m.Semanas, semana)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Meses devuelve el mes que corre y los siguientes, que son los que se pueden
|
|
// pedir.
|
|
func (v Vista) Meses(cuantos int) []Mes {
|
|
t := v.hoy.MustTime()
|
|
var out []Mes
|
|
for i := 0; i < cuantos; i++ {
|
|
m := t.AddDate(0, i, 0)
|
|
out = append(out, v.Mes(m.Year(), int(m.Month())))
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Proximas lista los eventos que vienen, para la tabla de debajo del mes.
|
|
func (v Vista) Proximas() []Evento {
|
|
var out []Evento
|
|
for _, e := range v.eventos {
|
|
if e.Estado.Ocupa() && e.Hasta >= v.hoy {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// etiquetaEstado distingue lo que ha pedido la asamblea de externas mientras
|
|
// espera: una vez confirmado es un evento más.
|
|
func etiquetaEstado(e Estado, externas bool) string {
|
|
if externas && e == Solicitado {
|
|
return "SOLICITADO EXTERNAS"
|
|
}
|
|
return e.Etiqueta()
|
|
}
|
|
|
|
func diasDelMes(anio, mes int) int {
|
|
return fechaMes(anio, mes).AddDate(0, 1, -1).Day()
|
|
}
|
|
|
|
func fechaMes(anio, mes int) time.Time {
|
|
return time.Date(anio, time.Month(mes), 1, 0, 0, 0, 0, time.Local)
|
|
}
|