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 Enre: solicita y amadrina // RolExternas es la cuenta de la asamblea de externas: apunta los eventos // de colectivos de fuera que se hablan allí, sin cupo, y la asamblea de la // Enre los confirma. RolExternas Rol = "externas" ) func (r Rol) Valido() bool { return r == RolEnre || r == RolInterno || r == RolExternas } 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 // Aviso o error de la siguiente página. Van por la sesión y no por la URL: // con «?a=» cualquiera podía fabricar un enlace de laenre.net que // enseñara el texto que quisiera. Aviso string Error string }