Add credential-free sample mode with sample data, README, and doc reconciliation
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Heliosian
|
||||
|
||||
Web apps for the Helios school community (K-8), served as one static Go binary on Cloud Run. The first app is the school directory, "Helios Who?". Built in the open by community volunteers, mostly through coding agents; the repo contains no secrets and no real community data.
|
||||
|
||||
## Quick start
|
||||
|
||||
brew install go
|
||||
go run .
|
||||
|
||||
Open http://localhost:8080. That's the whole setup: with no configuration the server loads the fictional community in `sampledata/`, signs every request in as a sample parent, and fakes geocoding — no credentials, no cloud project. Templates, static assets, and sample data are read from disk on every request, so edit a file and refresh; nothing needs restarting.
|
||||
|
||||
`brew install --cask google-chrome` additionally enables the screenshot tooling used to verify visual changes ([docs/screenshots.md](docs/screenshots.md)). No Node, no Docker. Go 1.26 or later.
|
||||
|
||||
To run against real community data instead, see [docs/dev.md](docs/dev.md).
|
||||
|
||||
## Layout
|
||||
|
||||
- `main.go` — entry point and app wiring
|
||||
- `internal/auth` — Google sign-in and session cookies
|
||||
- `internal/data` — tabular data sources: sample CSVs and Google Sheets
|
||||
- `internal/directory` — the directory app: model load, handlers, self-service edits
|
||||
- `internal/blob` — media from Google Drive, held in memory with thumbnails
|
||||
- `internal/geocode` — address → coordinates for the map
|
||||
- `web/` — page templates and static assets (frameworkless JavaScript)
|
||||
- `sampledata/` — the fictional community served by default
|
||||
- `tools/` — dev tooling: screenshots, browser driving, sheet inspection
|
||||
- `docs/` — everything below
|
||||
|
||||
## Docs
|
||||
|
||||
- [goals.md](docs/goals.md) — what this is and the principles behind it
|
||||
- [dev.md](docs/dev.md) — local development, including real-data mode
|
||||
- [data.md](docs/data.md) — the directory data model and load pipeline
|
||||
- [directory.md](docs/directory.md) — the directory app spec
|
||||
- [design.md](docs/design.md) — palette, typography, brand
|
||||
- [pwa.md](docs/pwa.md) — installable-app wiring
|
||||
- [screenshots.md](docs/screenshots.md) — page capture for humans and agents
|
||||
- [deploy.md](docs/deploy.md) — production deployment
|
||||
- [plan.md](docs/plan.md) — what remains to build
|
||||
+1
-1
@@ -65,7 +65,7 @@ Original tile art (1254×1254 JPEGs for all nine classrooms and all nine grade t
|
||||
- Pill-shaped search inputs and filter buttons; circular icon buttons for quick actions (message, mail, map, favorite).
|
||||
- List rows with right chevrons; hairline dividers; generous whitespace.
|
||||
- Detail pages break the white page with a full-width deep-teal band for family content.
|
||||
- Inline separators: "▶" chains grade to team to subteam; "·" dots separate contact fragments.
|
||||
- Inline separators: "▶" chains grade to classroom to crew; "·" dots separate contact fragments.
|
||||
|
||||
## Responsive chrome
|
||||
|
||||
|
||||
+21
-26
@@ -1,42 +1,37 @@
|
||||
# Local development
|
||||
|
||||
Toolchain prerequisites: see [setup.md](setup.md).
|
||||
|
||||
## Run
|
||||
|
||||
go run .
|
||||
|
||||
The server listens on http://localhost:8080 (override with `PORT`). Templates, static assets, and sample data are read from disk on every request — edit a file and refresh the browser; no restart needed.
|
||||
http://localhost:8080 (override with `PORT`). With `DIRECTORY_SHEET` unset the server serves the fictional community in `sampledata/`, signs every request in as a sample parent, and geocodes with a deterministic fake — no credentials or configuration. Templates, static assets, and sample data are read from disk on every request; edit a file and refresh.
|
||||
|
||||
## Auth
|
||||
Sample-mode limits: the map section needs a real Maps JavaScript key (`GOOGLE_MAPS_BROWSER_KEY`) to render tiles, and self-service edits and media uploads need real-data mode — there is no writable backend or blob store behind the sample CSVs.
|
||||
|
||||
Everything — pages, static assets, and the API — sits behind Google sign-in restricted to the school's Google Workspace domain. Unauthenticated requests get the login page (API paths get a 401). The OAuth 2.0 Web application client (authorized JavaScript origins must include `http://localhost:8080` for local development) is read from `creds/oauth-client.json` — the JSON downloaded from the Cloud console — or from `GOOGLE_CLIENT_ID` when set; the server refuses to start with neither. After Google sign-in the server issues its own HMAC-signed session cookie; set `SESSION_KEY` to keep sessions valid across restarts and instances (without it each start generates a random key).
|
||||
`sampledata/` mirrors the production Sheets layout: one directory per app, one CSV per table, first row is the schema, served through the same data-source interface the Sheets backend implements. It stays fictional — real community data never goes here.
|
||||
|
||||
To capture authenticated pages with the screenshot tooling, launch the capture browser (`go run ./tools/capturebrowser`), sign in to the local server there once, and use `tools/browse` or `tools/screenshot -remote` — the session cookie lives in the capture profile. Plain `tools/screenshot` runs a fresh headless browser with no session and captures the login page.
|
||||
|
||||
## Maps
|
||||
|
||||
The map section geocodes family addresses server-side via the Google Geocoding API (results cached in memory per address) and renders in the browser with the Maps JavaScript API. Two API keys from a project with those APIs enabled, both required at startup:
|
||||
|
||||
- Server key — Geocoding API; restrict by server IP (or leave unrestricted for dev). Never rendered into pages. Read from `creds/geocoding.key`, or `GOOGLE_MAPS_SERVER_KEY` when set.
|
||||
- Browser key — Maps JavaScript API; rendered into the page, so restrict by HTTP referer (localhost and the serving domain). Read from `creds/maps.key`, or `GOOGLE_MAPS_BROWSER_KEY` when set.
|
||||
|
||||
## Local data
|
||||
|
||||
The server reads local data from `sampledata/`, mirroring the production Sheets layout: one directory per app, one CSV file per table, first row is the schema. It goes through the same data-source interface production backends implement, so app code never knows which backend it is talking to.
|
||||
In sample mode `tools/screenshot` captures pages directly, no session needed (see `docs/screenshots.md`).
|
||||
|
||||
## Real data
|
||||
|
||||
DIRECTORY_SHEET=<spreadsheet id> go run .
|
||||
|
||||
switches the directory app to the Google Sheets source. At startup the directory tables are read from the spreadsheet and normalized into the in-memory data model (see `docs/data.md`); the server refuses to start if that load fails, and the model reloads every five minutes. Requires the service account key at `creds/service-account.json` (the directory is gitignored) with the Sheets API enabled, the spreadsheet shared with the service account as an editor (self-service uploads write media cells and append to the Change Log tab), and the media shared drive shared as content manager (uploads create files and archive old versions). Real data never leaves the process: nothing is written to disk.
|
||||
serves from the production spreadsheet and media drive (see `docs/data.md`) and turns on the full stack. The model loads at startup — the server refuses to start if the load fails — and reloads every five minutes. Real data never leaves the process: nothing is written to disk. Requirements:
|
||||
|
||||
## Layout
|
||||
- **Sign-in** — everything sits behind Google sign-in restricted to the school's Workspace domain (API paths get a 401 instead of the login page). The OAuth web client is read from `creds/oauth-client.json` or `GOOGLE_CLIENT_ID`; its authorized JavaScript origins must include `http://localhost:8080`. The server issues its own HMAC-signed session cookie; set `SESSION_KEY` to keep sessions valid across restarts.
|
||||
- **Service account** — key at `creds/service-account.json` (the directory is gitignored), Sheets API enabled, the spreadsheet shared with it as editor (self-service edits write cells and append to the Change Log tab), the media shared drive shared as content manager (uploads create files and archive old versions).
|
||||
- **Maps** — a server key for the Geocoding API (`creds/geocoding.key` or `GOOGLE_MAPS_SERVER_KEY`; never rendered into pages, restrict by server IP or leave unrestricted for dev) and a browser key for the Maps JavaScript API (`creds/maps.key` or `GOOGLE_MAPS_BROWSER_KEY`; rendered into pages, restrict by HTTP referer). Geocoding results are cached in memory per address.
|
||||
|
||||
- `main.go` — server entry point and app routing
|
||||
- `internal/data` — data source interface and the CSV sample-data implementation
|
||||
- `internal/directory` — directory app handlers
|
||||
- `web/directory` — directory app page templates and static assets
|
||||
- `tools/screenshot` — dev-site page capture, see [screenshots.md](screenshots.md)
|
||||
- `tools/columns` — print the column names of each directory table in the configured source
|
||||
- `tools/splash` — extract the original app's iOS splash screens into `web/static/brand/splash`
|
||||
To capture authenticated real-data pages, launch the capture browser (`go run ./tools/capturebrowser`), sign in to the local server there once, and use `tools/browse` or `tools/screenshot -remote` — the session cookie lives in the capture profile.
|
||||
|
||||
## Setup
|
||||
|
||||
Development happens on macOS. Two Homebrew installs cover everything here and in `docs/screenshots.md`:
|
||||
|
||||
brew install go
|
||||
brew install --cask google-chrome
|
||||
|
||||
- **Go** 1.26 or later — builds and runs the server and all tooling (`go run`, `go vet`).
|
||||
- **Google Chrome** — launched headless by the screenshot tool from its standard install location; never opened by hand.
|
||||
|
||||
No Node, no Docker, and no cloud credentials are needed for local development. Repository layout is in the README.
|
||||
|
||||
+7
-15
@@ -5,11 +5,11 @@ The directory ("Helios Who?") is the community's who's-who: students, parents, a
|
||||
## Entities
|
||||
|
||||
- **Person** — first and last name; role (student, parent, staff); optional pronouns; optional nickname and pronunciation (an audio recording); photo (some people use an illustrated avatar instead); email; role-specific fields:
|
||||
- *Students*: grade, classroom and team assignment (displayed as a chain, e.g. grade ▶ team ▶ subteam), optional free-text "about me" written by or about the kid.
|
||||
- *Students*: grade, classroom, and crew (displayed as a chain, e.g. grade ▶ classroom ▶ crew), optional free-text "about me" written by or about the kid.
|
||||
- *Parents*: their kids (shown as context wherever the parent appears), optional room-parent assignments.
|
||||
- *Staff*: job title, displayed prominently; staff may have no family record.
|
||||
- **Family** — the join between adults and kids: combined surname(s), family photo with a caption identifying everyone in it, an optional family-name pronunciation recording, member list split into adults and kids, address, phone. Lists show the city; the full address powers map actions. Families choose how much address to share (full postal address or just the city).
|
||||
- **Classroom** — name and mascot artwork, the grade band it serves, and its students, staff, and parents. Classrooms nest teams/subteams that student rows reference.
|
||||
- **Classroom** — name and mascot artwork, the grade band it serves, and its students, staff, and parents. Classrooms nest crews that student rows reference.
|
||||
- **Grade** — K through 8, grouped into bands (K, 1st/2nd, 3rd/4th, ...) for browsing.
|
||||
|
||||
## Navigation
|
||||
@@ -22,14 +22,14 @@ Sections:
|
||||
|
||||
Four tabs, each with search and filter:
|
||||
|
||||
- **Everyone** — grid of circular photos. Each card: role label with pronouns (e.g. "PARENT (SHE/HER)"), name, and a context line — kids' names for parents, grade/team chain for students, job title for staff.
|
||||
- **Students** — larger cards, first name prominent over last name, grade/team chain, pronouns badge.
|
||||
- **Everyone** — grid of circular photos. Each card: role label with pronouns (e.g. "PARENT (SHE/HER)"), name, and a context line — kids' names for parents, grade/classroom chain for students, job title for staff.
|
||||
- **Students** — larger cards, first name prominent over last name, grade/classroom chain, pronouns badge.
|
||||
- **Families** — family-photo cards with grade badges, surname combination, and kids' first names.
|
||||
- **Staff** — grouped into sections (admin and office staff, teaching staff, ...), title over name.
|
||||
|
||||
### Person detail
|
||||
|
||||
Breadcrumb back to the list, favorite (heart) toggle, photo, role label with pronouns, name with nickname/pronunciation line, grade/team chain for students, email and address rows with quick actions (message, mail, map). Students add the "about me" paragraph. Below, a contrasting family band: the person's family name, a narrative caption of who's who, kid rows (grade/team, email), adult rows, and a link to the family page.
|
||||
Breadcrumb back to the list, favorite (heart) toggle, photo, role label with pronouns, name with nickname/pronunciation line, grade/classroom chain for students, email and address rows with quick actions (message, mail, map). Students add the "about me" paragraph. Below, a contrasting family band: the person's family name, a narrative caption of who's who, kid rows (grade/team, email), adult rows, and a link to the family page.
|
||||
|
||||
### Family detail
|
||||
|
||||
@@ -37,7 +37,7 @@ Family photo with click-to-expand and its identifying caption, grade badges, fam
|
||||
|
||||
### Classrooms
|
||||
|
||||
Three tabs: browse classrooms by grade band (mascot art, student count, link to detail), the same grouped by classroom, and room parents (parent rows annotated with each of their kids' classroom and grade). Classroom detail shows the mascot, name, and tabbed member lists — students (grouped by team, with parents' names above each student and the about-me blurb inline), staff, and parents — with per-tab counts.
|
||||
Three tabs: browse classrooms by grade band (mascot art, student count, link to detail), the same grouped by classroom, and room parents (parent rows annotated with each of their kids' classroom and grade). Classroom detail shows the mascot, name, and tabbed member lists — students (grouped by crew, with parents' names above each student and the about-me blurb inline), staff, and parents — with per-tab counts.
|
||||
|
||||
### My Family
|
||||
|
||||
@@ -55,14 +55,6 @@ A Google map of family locations: one brand-teal pin per geocoded family address
|
||||
|
||||
A copyable contact table for party planning and outreach: full name, email, role, grade, classroom. Tabs narrow to parents, students, both, or the user's bookmarked people. Filters select grades or classrooms.
|
||||
|
||||
### Data View
|
||||
|
||||
A raw tabular view over the underlying records, for power users.
|
||||
|
||||
### Share & About
|
||||
|
||||
Share the app by SMS or link, an explanation of why photos and facts are collected, a bug-report pointer, and an opt-out form for removing a person's information.
|
||||
|
||||
## Behaviors
|
||||
|
||||
- Everything is cross-linked: parents ↔ kids ↔ families ↔ classrooms; any person reference navigates to that person.
|
||||
@@ -70,4 +62,4 @@ Share the app by SMS or link, an explanation of why photos and facts are collect
|
||||
- Favorites/bookmarks mark people and feed the email list's bookmark tab.
|
||||
- Photos lazy-load; full-size view on click where the photo is the subject (family pages).
|
||||
- All data is community-only, behind sign-in; opt-out removes a person on request.
|
||||
- Self-service media: viewing your own record, your kids', or your family page shows inline edit icons — a camera on the photo for uploads, microphone/file icons under the pronunciation player to record in the browser or upload audio, and a pencil on the About Me text for inline editing. Replaced files move to an `archive` folder in the media drive with a timestamp, the sheet cells are updated, and every change appends to the sheet's `Change Log` tab (timestamp, actor, target, kind, new value, replaced value).
|
||||
- Self-service: viewing your own record, your kids', or your family page shows inline edit affordances — photo upload, pronunciation recording or upload, About Me text, preferred name, phone, address — plus opt-out for yourself or your kids. Media uploads replace the Drive file and archive the previous version; sheet-backed edits write the Overrides tab and append a Change Log row (see `docs/data.md`).
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Dev environment setup
|
||||
|
||||
Development happens on macOS. Two Homebrew installs, and everything in `docs/dev.md` and `docs/screenshots.md` works:
|
||||
|
||||
brew install go
|
||||
brew install --cask google-chrome
|
||||
|
||||
- **Go** 1.26 or later — builds and runs the server and all tooling (`go run`, `go vet`).
|
||||
- **Google Chrome** — launched headless by the screenshot tool; never needs to be opened by hand. The tool finds it in its standard install location automatically.
|
||||
|
||||
No Node, no Docker, and no cloud credentials are needed for local development.
|
||||
@@ -39,6 +39,12 @@ func Email(r *http.Request) string {
|
||||
return email
|
||||
}
|
||||
|
||||
func Fixed(email string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey{}, email)))
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Auth) Register(mux *http.ServeMux) {
|
||||
mux.HandleFunc("POST /auth/login", a.login)
|
||||
mux.HandleFunc("POST /auth/logout", a.logout)
|
||||
|
||||
@@ -11,15 +11,19 @@ import (
|
||||
|
||||
const refreshInterval = 5 * time.Minute
|
||||
|
||||
type Geocoder interface {
|
||||
Lookup(address string) (geocode.Point, error)
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
source data.Source
|
||||
geocoder *geocode.Client
|
||||
geocoder Geocoder
|
||||
blobs BlobChecker
|
||||
mu sync.RWMutex
|
||||
model *Model
|
||||
}
|
||||
|
||||
func NewCache(source data.Source, geocoder *geocode.Client, blobs BlobChecker) (*Cache, error) {
|
||||
func NewCache(source data.Source, geocoder Geocoder, blobs BlobChecker) (*Cache, error) {
|
||||
c := &Cache{source: source, geocoder: geocoder, blobs: blobs}
|
||||
if err := c.refresh(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package geocode
|
||||
|
||||
import "crypto/sha256"
|
||||
|
||||
type Fake struct{}
|
||||
|
||||
func (Fake) Lookup(address string) (Point, error) {
|
||||
sum := sha256.Sum256([]byte(address))
|
||||
return Point{
|
||||
Lat: 37.5 + (float64(sum[0])/255-0.5)*0.12,
|
||||
Lng: -122.45 + (float64(sum[1])/255-0.5)*0.12,
|
||||
}, nil
|
||||
}
|
||||
@@ -17,17 +17,7 @@ import (
|
||||
"heliosian/internal/geocode"
|
||||
)
|
||||
|
||||
func directorySource() data.Source {
|
||||
sheetID := os.Getenv("DIRECTORY_SHEET")
|
||||
if sheetID == "" {
|
||||
return data.Dir{Root: "sampledata"}
|
||||
}
|
||||
source, err := data.NewSheet(map[string]string{"directory": sheetID})
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] load directory sheet: %v", err)
|
||||
}
|
||||
return source
|
||||
}
|
||||
const sampleUser = "jordan.whitfield@heliosschool.org"
|
||||
|
||||
func sessionKey() []byte {
|
||||
if key := os.Getenv("SESSION_KEY"); key != "" {
|
||||
@@ -86,26 +76,34 @@ func main() {
|
||||
if err := mime.AddExtensionType(".webmanifest", "application/manifest+json"); err != nil {
|
||||
log.Fatalf("[ERROR] register manifest mime type: %v", err)
|
||||
}
|
||||
authn := auth.New(clientID(), sessionKey())
|
||||
serverKey := mapsKey("GOOGLE_MAPS_SERVER_KEY", "creds/geocoding.key")
|
||||
browserKey := mapsKey("GOOGLE_MAPS_BROWSER_KEY", "creds/maps.key")
|
||||
source := directorySource()
|
||||
sheetID := os.Getenv("DIRECTORY_SHEET")
|
||||
var source data.Source
|
||||
var geocoder directory.Geocoder = geocode.Fake{}
|
||||
browserKey := os.Getenv("GOOGLE_MAPS_BROWSER_KEY")
|
||||
var store *blob.Store
|
||||
var blobs directory.BlobChecker
|
||||
if os.Getenv("DIRECTORY_SHEET") != "" {
|
||||
var err error
|
||||
if sheetID == "" {
|
||||
source = data.Dir{Root: "sampledata"}
|
||||
log.Printf("DIRECTORY_SHEET not set, serving sample data as %s", sampleUser)
|
||||
} else {
|
||||
sheet, err := data.NewSheet(map[string]string{"directory": sheetID})
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] load directory sheet: %v", err)
|
||||
}
|
||||
source = sheet
|
||||
geocoder = geocode.New(mapsKey("GOOGLE_MAPS_SERVER_KEY", "creds/geocoding.key"))
|
||||
browserKey = mapsKey("GOOGLE_MAPS_BROWSER_KEY", "creds/maps.key")
|
||||
store, err = blob.New()
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] blob store: %v", err)
|
||||
}
|
||||
blobs = store
|
||||
}
|
||||
cache, err := directory.NewCache(source, geocode.New(serverKey), blobs)
|
||||
cache, err := directory.NewCache(source, geocoder, blobs)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] load directory data: %v", err)
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
authn.Register(mux)
|
||||
directory.Register(mux, cache, browserKey)
|
||||
if store != nil {
|
||||
blob.Register(mux, store)
|
||||
@@ -113,10 +111,19 @@ func main() {
|
||||
}
|
||||
mux.Handle("GET /{$}", http.RedirectHandler("/people", http.StatusFound))
|
||||
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
||||
var handler http.Handler = directory.MemberGate(cache, mux)
|
||||
if sheetID == "" {
|
||||
mux.Handle("POST /auth/logout", http.RedirectHandler("/", http.StatusSeeOther))
|
||||
handler = auth.Fixed(sampleUser, handler)
|
||||
} else {
|
||||
authn := auth.New(clientID(), sessionKey())
|
||||
authn.Register(mux)
|
||||
handler = authn.Wrap(handler)
|
||||
}
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
log.Printf("listening on http://localhost:%s", port)
|
||||
log.Fatal(http.ListenAndServe(":"+port, noCache(authn.Wrap(directory.MemberGate(cache, mux)))))
|
||||
log.Fatal(http.ListenAndServe(":"+port, noCache(handler)))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Name,Email
|
||||
Mia Torres,mia.torres@heliosschool.org
|
||||
Sam Whitfield,sam.whitfield@heliosschool.org
|
||||
|
@@ -0,0 +1,29 @@
|
||||
Email,Added,Full Name,Legal Name,Preferred Name,Is Student,Is Parent,Is Staff,New to Helios,Pronouns,Facts,Grade,Classroom,Crew,Phone,Job Title,Department,Grade Band,Room Parent,Address,Family Phone,Family Photo Caption,Opted Out
|
||||
dana.hawkins@heliosschool.org,,,,,,,TRUE,,she/her,,,,,,Art Teacher,Co-Curriculars and Specialists,,,,,,
|
||||
jordan.whitfield@heliosschool.org,,,,,,,,,they/them,,,,,,,,,Jayvens,,,,
|
||||
asha.chandra@heliosschool.org,,,,,,,,,,,,,,,,,,Cospreys,,,,
|
||||
harper.quinn@heliosschool.org,,,,,,,,,she/her,"Plays goalie in the coastside soccer league, and is building a solar-powered go-kart.",,,,,,,,,,,,
|
||||
owen.park@heliosschool.org,,,,,,,,,,Can name every bird on the bluff trail. Future marine biologist.,,,,,,,,,,,,
|
||||
felix.osei@heliosschool.org,,,,,,,,,he/him,,,,,,,,,,,,,
|
||||
noor.haddad@heliosschool.org,,,,,,,,TRUE,,,,,,,,,,,,,,
|
||||
layla.haddad@heliosschool.org,,,,,,,,TRUE,,,,,,,,,,,,,,
|
||||
colin.quinn@heliosschool.org,,,,,,,,,,,,,,-,,,,,,,,
|
||||
elena.torres@heliosschool.org,,,,,,,,,,,,,,,,,,,,,"Marco, Mia, and Elena at Mavericks Beach.",
|
||||
deepa.natarajan@heliosschool.org,,,,,,,,,,,,,,,,,,,,650-555-0199,,
|
||||
grace.kim@heliosschool.org,TRUE,Grace Kim,,,,,TRUE,,she/her,,,,,,Head of School,Admin and Office Staff,,,,,,
|
||||
bill.ryder@heliosschool.org,TRUE,Bill Ryder,,,,,TRUE,,,,,,,,Office Manager,Admin and Office Staff,,,,,,
|
||||
ruth.amari@heliosschool.org,TRUE,Ruth Amari,,,,,TRUE,,,"Twelve years teaching kindergarten, keeper of the class worm farm.",,Hummingbirds,,,Kindergarten Teacher,Classroom Teachers,Hummingbirds,,,,,
|
||||
miguel.santos@heliosschool.org,TRUE,Miguel Santos,,,,,TRUE,,,,,Hawks,,,1st Grade Teacher,Classroom Teachers,Halcons,,,,,
|
||||
alice.fontaine@heliosschool.org,TRUE,Alice Fontaine,,,,,TRUE,,,,,Falcons,,,2nd Grade Teacher,Classroom Teachers,Halcons,,,,,
|
||||
peter.okafor@heliosschool.org,TRUE,Peter Okafor,,,,,TRUE,,,,,Jays,,,3rd Grade Teacher,Classroom Teachers,Jayvens,,,,,
|
||||
susan.byrne@heliosschool.org,TRUE,Susan Byrne,,,,,TRUE,,,,,Ravens,,,4th Grade Teacher,Classroom Teachers,Jayvens,,,,,
|
||||
hana.ito@heliosschool.org,TRUE,Hana Ito,,,,,TRUE,,,,,Condors,Pinnacles,,5th/6th Grade Teacher,Classroom Teachers,Cospreys,,,,,
|
||||
marcus.bell@heliosschool.org,TRUE,Marcus Bell,,,,,TRUE,,,,,Condors,Big Sur,,5th/6th Grade Teacher,Classroom Teachers,Cospreys,,,,,
|
||||
lena.vogel@heliosschool.org,TRUE,Lena Vogel,,,,,TRUE,,,,,Ospreys,River,,5th/6th Grade Teacher,Classroom Teachers,Cospreys,,,,,
|
||||
tom.grady@heliosschool.org,TRUE,Tom Grady,,,,,TRUE,,,,,Ospreys,Sea,,5th/6th Grade Teacher,Classroom Teachers,Cospreys,,,,,
|
||||
ivy.chen@heliosschool.org,TRUE,Ivy Chen,,,,,TRUE,,she/her,,,Egrets,Snowy,,Humanities Teacher,Classroom Teachers,Hegrets,,,,,
|
||||
raj.malhotra@heliosschool.org,TRUE,Raj Malhotra,,,,,TRUE,,,,,Egrets,Great,,Science Teacher,Classroom Teachers,Hegrets,,,,,
|
||||
kate.doyle@heliosschool.org,TRUE,Kate Doyle,,,,,TRUE,,,,,Herons,Great Blue,,Humanities Teacher,Classroom Teachers,Hegrets,,,,,
|
||||
omar.farouk@heliosschool.org,TRUE,Omar Farouk,,,,,TRUE,,he/him,,,Herons,Green,,Science Teacher,Classroom Teachers,Hegrets,,,,,
|
||||
noa.adler@heliosschool.org,TRUE,Noa Adler,,,,,TRUE,,,,,,,,Music Teacher,Co-Curriculars and Specialists,,,,,,
|
||||
hank.morrow@heliosschool.org,TRUE,Hank Morrow,,,,,TRUE,,,,,,,,Facilities Manager,Facilities Staff,,,,,,
|
||||
|
@@ -0,0 +1,14 @@
|
||||
entry_sort_name,student_full_name,student_classifications,student_email,student_phone_mobile,household_1_phone,household_1_address,household_1_person_1_full_name,household_1_person_1_email,household_1_person_1_email_2,household_1_person_1_phone_mobile,household_1_person_1_phone_business,household_1_person_2_full_name,household_1_person_2_email,household_1_person_2_email_2,household_1_person_2_phone_mobile,household_1_person_2_phone_business,household_2_phone,household_2_address,household_2_person_1_full_name,household_2_person_1_email,household_2_person_1_email_2,household_2_person_1_phone_mobile,household_2_person_1_phone_business,household_2_person_2_full_name,household_2_person_2_email,household_2_person_2_email_2,household_2_person_2_phone_mobile,household_2_person_2_phone_business
|
||||
"Torres, Mia",Mia Torres,"{""grade_level"": ""Kindergarten"", ""homeroom"": ""Hummingbirds""}",,,650-555-0141,"214 Alder Ln, Half Moon Bay, CA 94019",Elena Torres,elena.torres@heliosschool.org,,650-555-0142,,Marco Torres,marco.torres@heliosschool.org,,650-555-0143,,,,,,,,,,,,,
|
||||
"Baxter, Leo",Leo (Leonardo) Baxter,"{""grade_level"": ""Grade 1"", ""homeroom"": ""Hawks""}",leo.baxter@heliosschool.org,,,"88 Cypress Ave, El Granada, CA 94018",April Baxter,April.Baxter@heliosschool.org,,650-555-0151,,,,,,,,,,,,,,,,,,
|
||||
"Natarajan, Priya",Priya Natarajan,"{""grade_level"": ""Grade 2"", ""homeroom"": ""Falcons""}",priya.natarajan@heliosschool.org,,,"402 Seabreeze Ct, Half Moon Bay, CA 94019",Deepa Natarajan,deepa.natarajan@heliosschool.org,,650-555-0128,,Karthik Natarajan,karthik.natarajan@heliosschool.org,,,650-555-0129,,,,,,,,,,,,
|
||||
"Whitfield, Sam",Sam Whitfield,"{""grade_level"": ""Grade 3"", ""homeroom"": ""Jays""}",,,650-555-0176,"17 Pelican Point Rd, Moss Beach, CA 94038",Jordan Whitfield,jordan.whitfield@heliosschool.org,,650-555-0177,,Robin Whitfield,robin.whitfield@heliosschool.org,,650-555-0178,,,,,,,,,,,,,
|
||||
"Haddad, Noor",Noor Haddad,"{""grade_level"": ""Grade 4"", ""homeroom"": ""Ravens""}",noor.haddad@heliosschool.org,,,"Pacifica, CA",Layla Haddad,layla.haddad@heliosschool.org,,650-555-0134,,,,,,,,,,,,,,,,,,
|
||||
"Park, Owen",Owen Park,"{""grade_level"": ""Grade 5"", ""homeroom"": ""Pinnacles Condors""}",owen.park@heliosschool.org,,,"590 Coral Reef Ave, Half Moon Bay, CA 94019",Mina Park,mina.park@heliosschool.org,,650-555-0111,,Daniel Park,daniel.park@heliosschool.org,,650-555-0112,,,,,,,,,,,,,
|
||||
"Marchetti, Lucia",Lucia Marchetti,"{""grade_level"": ""Grade 5"", ""homeroom"": ""Big Sur Condors""}",lucia.marchetti@heliosschool.org,,,"31 Miramar Dr, Half Moon Bay, CA 94019",Sofia Marchetti,sofia.marchetti@heliosschool.org,,650-555-0117,,Paolo Marchetti,paolo.marchetti@heliosschool.org,,650-555-0118,,,,,,,,,,,,,
|
||||
"Whitfield, Ella",Ella Whitfield,"{""grade_level"": ""Grade 6"", ""homeroom"": ""River Ospreys""}",ella.whitfield@heliosschool.org,,650-555-0176,"17 Pelican Point Rd, Moss Beach, CA 94038",Jordan Whitfield,jordan.whitfield@heliosschool.org,,650-555-0177,,Robin Whitfield,robin.whitfield@heliosschool.org,,650-555-0178,,,,,,,,,,,,,
|
||||
"Chandra, Dev",Dev Chandra,"{""grade_level"": ""Grade 6"", ""homeroom"": ""Sea Ospreys""}",dev.chandra@heliosschool.org,,650-555-0120,"76 Kelp Hollow Rd, Montara, CA 94037",Asha Chandra,asha.chandra@heliosschool.org,,650-555-0121,,,,,,,,"1109 Palmetto Ave, Pacifica, CA 94044",Rohan Chandra,rohan.chandra@heliosschool.org,,650-555-0122,,,,,,
|
||||
"Quinn, Harper",Harper Quinn,"{""grade_level"": ""Grade 7"", ""homeroom"": ""Snowy Egrets""}",harper.quinn@heliosschool.org,650-555-0303,,"9 Spindrift Way, El Granada, CA 94018",Dana Hawkins,dana.hawkins@heliosschool.org,,650-555-0160,,Colin Quinn,colin.quinn@heliosschool.org,,650-555-0161,,,,,,,,,,,,,
|
||||
"Alvarez, Mateo",Mateo Alvarez,"{""grade_level"": ""Grade 7"", ""homeroom"": ""Great Egrets""}",mateo.alvarez@heliosschool.org,650-555-0304,,"245 Le Conte Ave, Pacifica, CA 94044",Carmen Alvarez,carmen.alvarez@heliosschool.org,,650-555-0166,,,,,,,,,,,,,,,,,,
|
||||
"Lindqvist, Zoe",Zoe Lindqvist,"{""grade_level"": ""Grade 8"", ""homeroom"": ""Great Blue Herons""}",zoe.lindqvist@heliosschool.org,650-555-0305,,"630 Etheldore St, Moss Beach, CA 94038",Freja Lindqvist,freja.lindqvist@heliosschool.org,,650-555-0171,,Anders Lindqvist,anders.lindqvist@heliosschool.org,,650-555-0172,,,,,,,,,,,,,
|
||||
"Osei, Felix",Felix Osei,"{""grade_level"": ""Grade 8"", ""homeroom"": ""Green Herons""}",felix.osei@heliosschool.org,650-555-0306,,,Abena Osei,abena.osei@heliosschool.org,,650-555-0181,,,,,,,,,,,,,,,,,,
|
||||
|
Reference in New Issue
Block a user