Rename Section to Crew, stage the load pipeline, dedupe lookups and token minting, drop burned tools

This commit is contained in:
Ian Gulliver
2026-08-16 13:21:25 -07:00
parent 8c9c1a6cd6
commit 2b836367e1
17 changed files with 389 additions and 530 deletions
+3 -8
View File
@@ -2,14 +2,13 @@
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"flag"
"fmt"
"log"
"os"
"time"
"heliosian/internal/auth"
)
func main() {
@@ -19,9 +18,5 @@ func main() {
if key == "" || *email == "" {
log.Fatal("[ERROR] SESSION_KEY and -email are required")
}
payload := fmt.Sprintf("%s|%d", *email, time.Now().Add(24*time.Hour).Unix())
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(payload))
fmt.Println(base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." +
base64.RawURLEncoding.EncodeToString(mac.Sum(nil)))
fmt.Println(auth.Token([]byte(key), *email, time.Now().Add(24*time.Hour)))
}
+13 -13
View File
@@ -5,12 +5,21 @@ import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"heliosian/internal/data"
"heliosian/internal/directory"
)
type staticFiles struct{}
func (staticFiles) Has(key string) bool {
_, err := os.Stat(filepath.Join("web/static", filepath.FromSlash(key)))
return err == nil
}
func main() {
sheet := flag.String("sheet", "", "spreadsheet id")
flag.Parse()
@@ -21,7 +30,7 @@ func main() {
if err != nil {
log.Fatalf("[ERROR] sheet source: %v", err)
}
model, err := directory.LoadModel(source, nil)
model, err := directory.LoadModel(source, nil, staticFiles{})
if err != nil {
log.Fatalf("[ERROR] load model: %v", err)
}
@@ -56,11 +65,11 @@ func main() {
}
fmt.Println("classrooms:")
for _, c := range model.Classrooms {
fmt.Printf(" %s (image %v, crews %v)\n", c.Name, c.ImageURL != "", c.HasSections)
fmt.Printf(" %s (image %v, crews %v)\n", c.Name, c.ImageURL != "", c.HasCrews)
}
fmt.Println("crews:")
for _, s := range model.Sections {
fmt.Printf(" %s | %s | %s | teachers %v\n", s.Classroom, s.Name, s.GradeBand, s.Teachers)
for _, c := range model.Crews {
fmt.Printf(" %s | %s | %s | teachers %v\n", c.Classroom, c.Name, c.GradeBand, c.Teachers)
}
bands := []string{}
for band := range model.RoomParents {
@@ -76,13 +85,4 @@ func main() {
for _, g := range model.Grades {
fmt.Printf(" %s -> %s (%s -> %s)\n", g.Name, g.NextName, g.Band, g.NextBand)
}
for _, email := range []string{"lexi.augenbergs@heliosschool.org", "daren.liang@heliosschool.org", "yeada.li@heliosschool.org", "dog@heliosschool.org", "mike.orlando@heliosschool.org", "evie.weiss@heliosschool.org"} {
for _, p := range model.People {
if p.Email == email {
fmt.Printf("spot %s: full=%q legal=%q pref=%q roles=S%v/P%v/T%v grade=%q class=%q crew=%q band=%q dept=%q title=%q family=%s\n",
email, p.FullName, p.LegalName, p.PreferredName, p.IsStudent, p.IsParent, p.IsStaff,
p.Grade, p.Classroom, p.Section, p.GradeBand, p.Department, p.JobTitle, p.FamilyKey)
}
}
}
}
-81
View File
@@ -1,81 +0,0 @@
// Command oneoff appends the Opted Out column to the Overrides and Change Log tabs.
package main
import (
"context"
"flag"
"fmt"
"log"
"heliosian/internal/data"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
)
func main() {
sheet := flag.String("sheet", "", "spreadsheet id")
flag.Parse()
if *sheet == "" {
log.Fatal("[ERROR] -sheet <spreadsheet id> is required")
}
svc, err := sheets.NewService(context.Background(),
option.WithCredentialsFile(data.KeyFile),
option.WithScopes(sheets.SpreadsheetsScope))
if err != nil {
log.Fatalf("[ERROR] create sheets client: %v", err)
}
meta, err := svc.Spreadsheets.Get(*sheet).Fields("sheets(properties(sheetId,title,gridProperties(columnCount)))").Do()
if err != nil {
log.Fatalf("[ERROR] get spreadsheet: %v", err)
}
for _, tab := range []string{"Overrides", "Change Log"} {
var props *sheets.SheetProperties
for _, s := range meta.Sheets {
if s.Properties.Title == tab {
props = s.Properties
}
}
if props == nil {
log.Fatalf("[ERROR] no %s tab", tab)
}
resp, err := svc.Spreadsheets.Values.Get(*sheet, fmt.Sprintf("'%s'!1:1", tab)).Do()
if err != nil {
log.Fatalf("[ERROR] read %s header: %v", tab, err)
}
width := len(resp.Values[0])
for _, cell := range resp.Values[0] {
if fmt.Sprint(cell) == "Opted Out" {
log.Fatalf("[ERROR] %s already has an Opted Out column", tab)
}
}
if int64(width) >= props.GridProperties.ColumnCount {
_, err = svc.Spreadsheets.BatchUpdate(*sheet, &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{{AppendDimension: &sheets.AppendDimensionRequest{
SheetId: props.SheetId,
Dimension: "COLUMNS",
Length: 1,
}}},
}).Do()
if err != nil {
log.Fatalf("[ERROR] widen %s: %v", tab, err)
}
}
cell := fmt.Sprintf("'%s'!%s1", tab, columnName(width))
_, err = svc.Spreadsheets.Values.Update(*sheet, cell, &sheets.ValueRange{
Values: [][]interface{}{{"Opted Out"}},
}).ValueInputOption("RAW").Do()
if err != nil {
log.Fatalf("[ERROR] write %s header: %v", tab, err)
}
log.Printf("added Opted Out to %s at %s", tab, cell)
}
}
func columnName(idx int) string {
name := ""
for idx >= 0 {
name = string(rune('A'+idx%26)) + name
idx = idx/26 - 1
}
return name
}
-126
View File
@@ -1,126 +0,0 @@
// Command renamefamilyblobs renames family media in the drive from parent-email names to family key hashes.
package main
import (
"context"
"flag"
"fmt"
"log"
"path"
"strings"
"heliosian/internal/data"
"heliosian/internal/directory"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
)
const folderMime = "application/vnd.google-apps.folder"
func main() {
sheet := flag.String("sheet", "", "spreadsheet id")
flag.Parse()
if *sheet == "" {
log.Fatal("[ERROR] -sheet <spreadsheet id> is required")
}
source, err := data.NewSheet(map[string]string{"directory": *sheet})
if err != nil {
log.Fatalf("[ERROR] sheet source: %v", err)
}
model, err := directory.LoadModel(source, nil)
if err != nil {
log.Fatalf("[ERROR] load model: %v", err)
}
localToHash := map[string]string{}
for _, family := range model.Families {
for _, adult := range family.AdultEmails {
local, _, _ := strings.Cut(adult, "@")
if existing, ok := localToHash[local]; ok && existing != family.Key {
log.Fatalf("[ERROR] adult local part %s maps to two families", local)
}
localToHash[local] = family.Key
}
}
svc, err := drive.NewService(context.Background(),
option.WithCredentialsFile(data.KeyFile),
option.WithScopes(drive.DriveScope))
if err != nil {
log.Fatalf("[ERROR] drive client: %v", err)
}
drives, err := svc.Drives.List().Do()
if err != nil {
log.Fatalf("[ERROR] list shared drives: %v", err)
}
if len(drives.Drives) != 1 {
log.Fatalf("[ERROR] expected one shared drive, found %d", len(drives.Drives))
}
folderList, err := svc.Files.List().
Q(fmt.Sprintf("name = 'families' and '%s' in parents and mimeType = '%s' and trashed = false", drives.Drives[0].Id, folderMime)).
SupportsAllDrives(true).IncludeItemsFromAllDrives(true).Corpora("allDrives").
Fields("files(id)").Do()
if err != nil {
log.Fatalf("[ERROR] find families folder: %v", err)
}
if len(folderList.Files) != 1 {
log.Fatalf("[ERROR] expected one families folder, found %d", len(folderList.Files))
}
folderID := folderList.Files[0].Id
renamed, kept, unknown := 0, 0, 0
token := ""
for {
call := svc.Files.List().
Q(fmt.Sprintf("'%s' in parents and trashed = false", folderID)).
SupportsAllDrives(true).IncludeItemsFromAllDrives(true).Corpora("allDrives").
Fields("nextPageToken, files(id, name, mimeType)").PageSize(1000)
if token != "" {
call = call.PageToken(token)
}
list, err := call.Do()
if err != nil {
log.Fatalf("[ERROR] list families folder: %v", err)
}
for _, f := range list.Files {
if f.MimeType == folderMime {
continue
}
ext := path.Ext(f.Name)
base := strings.TrimSuffix(f.Name, ext)
kind := ""
for _, k := range []string{"-photo", "-pronunciation"} {
if strings.HasSuffix(base, k) {
kind = k
}
}
if kind == "" {
log.Printf("[ERROR] unrecognized file name %q, leaving it", f.Name)
unknown++
continue
}
local := strings.TrimSuffix(base, kind)
hash, ok := localToHash[local]
if !ok {
if _, isCurrent := model.Families[local]; isCurrent {
kept++
continue
}
log.Printf("[ERROR] file %q matches no family adult, leaving it", f.Name)
unknown++
continue
}
newName := hash + kind + ext
_, err := svc.Files.Update(f.Id, &drive.File{Name: newName}).SupportsAllDrives(true).Do()
if err != nil {
log.Fatalf("[ERROR] rename %q to %q: %v", f.Name, newName, err)
}
log.Printf("renamed %q -> %q", f.Name, newName)
renamed++
}
if list.NextPageToken == "" {
break
}
token = list.NextPageToken
}
log.Printf("done: %d renamed, %d already keyed by hash, %d left untouched", renamed, kept, unknown)
}
+3 -8
View File
@@ -2,9 +2,6 @@
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"flag"
"fmt"
"log"
@@ -12,6 +9,8 @@ import (
"os/exec"
"strings"
"time"
"heliosian/internal/auth"
)
func main() {
@@ -56,11 +55,7 @@ func main() {
time.Sleep(time.Second)
}
payload := fmt.Sprintf("%s|%d", *email, time.Now().Add(24*time.Hour).Unix())
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(payload))
cookie := base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." +
base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
cookie := auth.Token([]byte(key), *email, time.Now().Add(24*time.Hour))
content, _ := os.ReadFile("/tmp/heliosian-server.log")
for _, line := range strings.Split(strings.TrimSpace(string(content)), "\n") {