From c3612a3b9357e4132a889f69351b5fb9302771bc Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Fri, 24 May 2024 20:12:57 -0600 Subject: [PATCH] templates: render static pages using templ Signed-off-by: Sumner Evans --- flake.lock | 6 +- go.mod | 1 + go.sum | 2 + internal/application.go | 55 +++- internal/archive.go | 359 +++++++++++------------ internal/contextkeys/keys.go | 10 + internal/templates/archive.templ | 145 +++++++++ internal/templates/authors.templ | 65 ++++ internal/templates/base.templ | 54 ++++ internal/templates/faq.templ | 103 +++++++ internal/templates/home.templ | 156 ++++++++++ internal/templates/info.templ | 94 ++++++ internal/templates/partials/footer.templ | 18 +- internal/templates/partials/navbar.templ | 55 +++- internal/templates/partials/pagenames.go | 13 + internal/templates/register.templ | 77 +++++ internal/templates/rules.templ | 73 +++++ website/templates/archive.html | 78 ----- website/templates/authors.html | 66 ----- website/templates/faq.html | 94 ------ website/templates/home.html | 132 --------- website/templates/info.html | 78 ----- website/templates/register.html | 76 ----- website/templates/rules.html | 71 ----- 24 files changed, 1059 insertions(+), 822 deletions(-) create mode 100644 internal/contextkeys/keys.go create mode 100644 internal/templates/archive.templ create mode 100644 internal/templates/authors.templ create mode 100644 internal/templates/faq.templ create mode 100644 internal/templates/home.templ create mode 100644 internal/templates/info.templ create mode 100644 internal/templates/partials/pagenames.go create mode 100644 internal/templates/register.templ create mode 100644 internal/templates/rules.templ delete mode 100644 website/templates/archive.html delete mode 100644 website/templates/authors.html delete mode 100644 website/templates/faq.html delete mode 100644 website/templates/home.html delete mode 100644 website/templates/info.html delete mode 100644 website/templates/register.html delete mode 100644 website/templates/rules.html diff --git a/flake.lock b/flake.lock index aa1fdb4..29bf318 100644 --- a/flake.lock +++ b/flake.lock @@ -96,11 +96,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716293225, - "narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=", + "lastModified": 1716330097, + "narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916", + "rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2", "type": "github" }, "original": { diff --git a/go.mod b/go.mod index 7cc5a5a..81b89b2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.22.0 require ( github.com/a-h/templ v0.2.697 + github.com/beeper/libserv v0.0.0-20231231202820-c7303abfc32c github.com/golang-jwt/jwt/v4 v4.5.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/rs/zerolog v1.32.0 diff --git a/go.sum b/go.sum index 252e699..fea993e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/a-h/templ v0.2.697 h1:OILxtWvD0NRJaoCOiZCopRDPW8paroKlGsrAiHLykNE= github.com/a-h/templ v0.2.697/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8= +github.com/beeper/libserv v0.0.0-20231231202820-c7303abfc32c h1:WqjRVgUO039eiISCjsZC4F9onOEV93DJAk6v33rsZzY= +github.com/beeper/libserv v0.0.0-20231231202820-c7303abfc32c/go.mod h1:b9FFm9y4mEm36G8ytVmS1vkNzJa0KepmcdVY+qf7qRU= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/internal/application.go b/internal/application.go index 798c49e..d6193be 100644 --- a/internal/application.go +++ b/internal/application.go @@ -1,18 +1,24 @@ package internal import ( + "context" "fmt" "html/template" "net/http" "regexp" "strings" + "github.com/a-h/templ" + "github.com/beeper/libserv/pkg/requestlog" "github.com/rs/zerolog" "github.com/rs/zerolog/hlog" "github.com/sendgrid/sendgrid-go" "github.com/ColoradoSchoolOfMines/mineshspc.com/database" "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/config" + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/contextkeys" + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/templates" + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/templates/partials" "github.com/ColoradoSchoolOfMines/mineshspc.com/website" ) @@ -91,6 +97,19 @@ type renderInfo struct { RedirectIfLoggedIn bool } +func (a *Application) SettingsContextMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + user, err := a.GetLoggedInTeacher(r) + if err == nil { + ctx = context.WithValue(ctx, contextkeys.ContextKeyLoggedInTeacher, user) + } + ctx = context.WithValue(ctx, contextkeys.ContextKeyRegistrationEnabled, a.Config.RegistrationEnabled) + ctx = context.WithValue(ctx, contextkeys.ContextKeyHostedByHTML, a.Config.HostedByHTML) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + func (a *Application) Start() { a.Log.Info().Msg("connecting to sendgrid") a.SendGridClient = sendgrid.NewSendClient(a.Config.SendgridAPIKey) @@ -98,29 +117,34 @@ func (a *Application) Start() { a.Log.Info().Msg("Starting router") router := http.NewServeMux() + router.Handle("GET /static/", http.FileServer(http.FS(website.StaticFS))) // Serve static files noArgs := func(r *http.Request) map[string]any { return nil } // Static pages staticPages := map[string]struct { - Template string - ArgGenerator func(r *http.Request) map[string]any + title string + pageName partials.PageName + content templ.Component }{ - "/{$}": {"home.html", noArgs}, - "/info": {"info.html", noArgs}, - "/authors": {"authors.html", noArgs}, - "/rules": {"rules.html", noArgs}, - "/register": {"register.html", noArgs}, - "/faq": {"faq.html", noArgs}, - "/archive": {"archive.html", a.GetArchiveTemplate}, + "GET /{$}": {"Home", partials.PageNameHome, templates.Home()}, + "GET /info/": {"Info", partials.PageNameInfo, templates.Info()}, + "GET /authors/": {"Authors", "", templates.Authors()}, + "GET /rules/": {"Rules", partials.PageNameRules, templates.Rules()}, + "GET /register/": {"Register", partials.PageNameRegister, templates.Register()}, + "GET /faq/": {"FAQ", partials.PageNameFAQ, templates.FAQ()}, + "GET /archive/": {"Archive", partials.PageNameArchive, templates.Archive(archiveInfo)}, } - for path, templateInfo := range staticPages { - router.HandleFunc("GET "+path, a.ServeTemplate(a.Log, templateInfo.Template, templateInfo.ArgGenerator)) + for path, pageInfo := range staticPages { + router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if len(pageInfo.pageName) > 0 { + ctx = context.WithValue(ctx, contextkeys.ContextKeyPageName, pageInfo.pageName) + } + templates.Base(pageInfo.title, pageInfo.content).Render(ctx, w) + }) } - // Serve static files - router.Handle("GET /static/", http.FileServer(http.FS(website.StaticFS))) - // Redirect pages redirects := map[string]string{ "/register/teacher": "/register/teacher/createaccount", @@ -243,7 +267,10 @@ func (a *Application) Start() { router.HandleFunc("GET /volunteer/scan", a.ServeTemplate(a.Log, "volunteerscan.html", a.GetVolunteerScanTemplate)) router.HandleFunc("GET /volunteer/checkin", a.HandleVolunteerCheckIn) + // Middleware goes from bottom up because it's doing function composition. var handler http.Handler = router + handler = a.SettingsContextMiddleware(handler) + handler = requestlog.AccessLogger(false)(handler) handler = hlog.RequestIDHandler("request_id", "RequestID")(handler) handler = hlog.NewHandler(*a.Log)(handler) diff --git a/internal/archive.go b/internal/archive.go index 7c8a73b..74ac427 100644 --- a/internal/archive.go +++ b/internal/archive.go @@ -1,227 +1,198 @@ package internal import ( - "net/http" + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/templates" ) -type Link struct { - URL string - Title string -} - -type WinningTeam struct { - Place string - Name string - School string - Location string -} - -type CompetitionResult struct { - Name string - Shortname string - Teams []WinningTeam -} - -type YearInfo struct { - Year int - RecapParagraphs []string - Links []Link - Results []CompetitionResult -} - -func (a *Application) GetArchiveTemplate(*http.Request) map[string]any { - return map[string]any{ - "YearInfo": []YearInfo{ +var archiveInfo = []templates.YearInfo{ + { + Year: 2024, + RecapParagraphs: []string{ + "The 2024 competition returned to an in-person only competition, but we also had an open division. We gave a separate set of prizes for teams consisting of only first-time competitors. We did not award prizes for the open division.", + "The in-person competition had 27 teams while the open division had 31 teams.", + }, + Links: []templates.Link{ + {URL: "/static/2024-solutions.pdf", Title: "Solution Sketch Slides"}, + {URL: "https://sumnerevans.com/posts/school/2024-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines-hspc.kattis.com/contests/mines-hspc24/problems", Title: "Problems"}, + }, + Results: []templates.CompetitionResult{ { - Year: 2024, - RecapParagraphs: []string{ - "The 2024 competition returned to an in-person only competition, but we also had an open division. We gave a separate set of prizes for teams consisting of only first-time competitors. We did not award prizes for the open division.", - "The in-person competition had 27 teams while the open division had 31 teams.", - }, - Links: []Link{ - {"/static/2024-solutions.pdf", "Solution Sketch Slides"}, - {"https://sumnerevans.com/posts/school/2024-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines-hspc.kattis.com/contests/mines-hspc24/problems", "Problems"}, - }, - Results: []CompetitionResult{ - { - Name: "Overall Winners", - Shortname: "Overall", - Teams: []WinningTeam{ - {"1st", "Innovation Center 1", "Innovation Center SVVSD", "Longmont"}, - {"2nd", "Sigma Scripters", "Arapahoe High School", "Centennial"}, - {"3nd", "CyberRebels2", "Columbine High School", "Littleton"}, - }, - }, - { - Name: "First-Time Team Winners", - Shortname: "FirstTime", - Teams: []WinningTeam{ - {"1st", "Loopy Groupies", "Chatfield Senior High School", "Littleton"}, - {"2nd", "Lorem Ipsum", "Warren Tech", "Lakewood"}, - {"3nd", "the cows(mooooooooooooo)", "Cherry Creek High School", "Greenwood Village"}, - }, - }, + Name: "Overall Winners", + Shortname: "Overall", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Innovation Center 1", School: "Innovation Center SVVSD", Location: "Longmont"}, + {Place: "2nd", Name: "Sigma Scripters", School: "Arapahoe High School", Location: "Centennial"}, + {Place: "3nd", Name: "CyberRebels2", School: "Columbine High School", Location: "Littleton"}, }, }, { - Year: 2023, - RecapParagraphs: []string{ - "The 2023 competition again featured two divisions: beginner and advanced. As with 2022, it was a hybrid competition, but we awarded prizes for both in-person and remote winners in both divisions.", - "The advanced division featured 31 teams, while the beginner division had 34 teams.", - }, - Links: []Link{ - {"/static/2023-solutions.pdf", "Solution Sketch Slides"}, - {"https://sumnerevans.com/posts/school/2023-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines23advanced.kattis.com/problems", "Advanced Problems"}, - {"https://mines23beginner.kattis.com/problems", "Beginner Problems"}, - }, - Results: []CompetitionResult{ - { - Name: "Advanced In-Person", - Shortname: "AdvancedInPerson", - Teams: []WinningTeam{ - {"1st", "Code Rats", "Futures Lab", "Fort Collins, Colorado"}, - {"2nd", "The Spanish Inquisition", "Regis Jesuit High School", "Aurora, Colorado"}, - {"3nd", "CA is 202", "Colorado Academy", "Denver, Colorado"}, - }, - }, - { - Name: "Beginner In-Person", - Shortname: "BeginnerInPerson", - Teams: []WinningTeam{ - {"1st", "Spaghetti Code and Meatballs", "Warren Tech", "Lakewood, Colorado"}, - {"2nd", "Innovation Center 1", "Innovation Center SVVSD", "Longmont, Colorado"}, - {"3nd", "Team LuLo", "Colorado Academy", "Denver, Colorado"}, - }, - }, - { - Name: "Advanced Remote", - Shortname: "AdvancedRemote", - Teams: []WinningTeam{ - {"1st", "River Hill Team #1", "River Hill High School", "Clarksville, Maryland"}, - {"2nd", "CreekCyberBruins", "Cherry Creek High School", "Greenwood Village, Colorado"}, - {"3nd", "JMS", "Bergen County Academies", "Bergen County, New Jersey"}, - }, - }, - { - Name: "Beginner Remote", - Shortname: "BeginnerRemote", - Teams: []WinningTeam{ - {"1st", "Wormhole", "Voice of Calling NPO", "Northridge, California"}, - {"2nd", "Lineup", "Voice of Calling NPO", "Northridge, California"}, - {"3nd", "River Hill Team #2", "River Hill High School", "Clarksville, Maryland"}, - }, - }, + Name: "First-Time Team Winners", + Shortname: "FirstTime", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Loopy Groupies", School: "Chatfield Senior High School", Location: "Littleton"}, + {Place: "2nd", Name: "Lorem Ipsum", School: "Warren Tech", Location: "Lakewood"}, + {Place: "3nd", Name: "the cows(mooooooooooooo)", School: "Cherry Creek High School", Location: "Greenwood Village"}, }, }, + }, + }, + { + Year: 2023, + RecapParagraphs: []string{ + "The 2023 competition again featured two divisions: beginner and advanced. As with 2022, it was a hybrid competition, but we awarded prizes for both in-person and remote winners in both divisions.", + "The advanced division featured 31 teams, while the beginner division had 34 teams.", + }, + Links: []templates.Link{ + {URL: "/static/2023-solutions.pdf", Title: "Solution Sketch Slides"}, + {URL: "https://sumnerevans.com/posts/school/2023-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines23advanced.kattis.com/problems", Title: "Advanced Problems"}, + {URL: "https://mines23beginner.kattis.com/problems", Title: "Beginner Problems"}, + }, + Results: []templates.CompetitionResult{ { - Year: 2022, - RecapParagraphs: []string{ - "The 2022 competition was the first to feature two divisions: a beginner division and an advanced division. It was also the first hybrid competition with both remote and in-person contestants.", - "The advanced division had 26 teams, while the beginner division had 39 teams. Due to the number of teams, we decided to give awards to first place through fourth place.", - }, - Links: []Link{ - {"https://sumnerevans.com/posts/school/2022-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines22advanced.kattis.com/problems", "Advanced Problems"}, - {"https://mines22beginner.kattis.com/problems", "Beginner Problems"}, - }, - Results: []CompetitionResult{ - { - Name: "Advanced", - Teams: []WinningTeam{ - {"1st", "Pen A Team", "PEN Academy", "Cresskill, New Jersey"}, - {"2nd", "Cherry Creek Cobras", "Cherry Creek High School", "Greenwood Village, Colorado"}, - {"3nd", "River Hill Team 1", "River Hill High School", "Clarksville, Maryland"}, - {"4th", "The Spanish Inquisition", "Regis Jesuit High School", "Aurora, Colorado"}, - }, - }, - { - Name: "Beginner", - Teams: []WinningTeam{ - {"1st", "LLL", "Future Forward at Bollman", "Thornton, Colorado"}, - {"2nd", "Error 404: Name not found", "Colorado Academy", "Denver, Colorado"}, - {"3nd", "Liberty 1", "Liberty Common School", "Fort Collins, Colorado"}, - {"4th", "Cool Cats", "Arvada West High School", "Arvada, Colorado"}, - }, - }, + Name: "Advanced In-Person", + Shortname: "AdvancedInPerson", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Code Rats", School: "Futures Lab", Location: "Fort Collins, Colorado"}, + {Place: "2nd", Name: "The Spanish Inquisition", School: "Regis Jesuit High School", Location: "Aurora, Colorado"}, + {Place: "3nd", Name: "CA is 202", School: "Colorado Academy", Location: "Denver, Colorado"}, }, }, { - Year: 2021, - RecapParagraphs: []string{ - "The 2021 competition was an all-remote competition featuring 55 teams from across the nation.", - }, - Links: []Link{ - {"https://sumnerevans.com/posts/school/2021-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines21.kattis.com/problems", "Problems"}, - }, - Results: []CompetitionResult{ - { - Teams: []WinningTeam{ - {"1st", "River Hill HS Team 1", "River Hill High School", "Clarksville, Maryland"}, - {"2nd", "PEN A Team", "PEN Academy", "Cresskill, New Jersey"}, - {"3nd", "River Hill HS Team 2", "River Hill High School", "Clarksville, Maryland"}, - }, - }, + Name: "Beginner In-Person", + Shortname: "BeginnerInPerson", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Spaghetti Code and Meatballs", School: "Warren Tech", Location: "Lakewood, Colorado"}, + {Place: "2nd", Name: "Innovation Center 1", School: "Innovation Center SVVSD", Location: "Longmont, Colorado"}, + {Place: "3nd", Name: "Team LuLo", School: "Colorado Academy", Location: "Denver, Colorado"}, }, }, { - Year: 2020, - RecapParagraphs: []string{ - "Due to COVID, the 2020 competition was the first all-remote HSPC competition. The competition featured 30 teams.", + Name: "Advanced Remote", + Shortname: "AdvancedRemote", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "River Hill Team #1", School: "River Hill High School", Location: "Clarksville, Maryland"}, + {Place: "2nd", Name: "CreekCyberBruins", School: "Cherry Creek High School", Location: "Greenwood Village, Colorado"}, + {Place: "3nd", Name: "JMS", School: "Bergen County Academies", Location: "Bergen County, New Jersey"}, }, - Links: []Link{ - {"https://sumnerevans.com/posts/school/2020-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines20.kattis.com/problems", "Problems"}, - }, - Results: []CompetitionResult{ - { - Teams: []WinningTeam{ - {"1st", "Installation Wizards", "STEM School Highlands Ranch", "Highlands Ranch, Colorado"}, - {"2nd", "i", "STEM School Highlands Ranch", "Highlands Ranch, Colorado"}, - {"3nd", "Sun Devils", "Kent Denver", "Denver, Colorado"}, - }, - }, + }, + { + Name: "Beginner Remote", + Shortname: "BeginnerRemote", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Wormhole", School: "Voice of Calling NPO", Location: "Northridge, California"}, + {Place: "2nd", Name: "Lineup", School: "Voice of Calling NPO", Location: "Northridge, California"}, + {Place: "3nd", Name: "River Hill Team #2", School: "River Hill High School", Location: "Clarksville, Maryland"}, }, }, + }, + }, + { + Year: 2022, + RecapParagraphs: []string{ + "The 2022 competition was the first to feature two divisions: a beginner division and an advanced division. It was also the first hybrid competition with both remote and in-person contestants.", + "The advanced division had 26 teams, while the beginner division had 39 teams. Due to the number of teams, we decided to give awards to first place through fourth place.", + }, + Links: []templates.Link{ + {URL: "https://sumnerevans.com/posts/school/2022-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines22advanced.kattis.com/problems", Title: "Advanced Problems"}, + {URL: "https://mines22beginner.kattis.com/problems", Title: "Beginner Problems"}, + }, + Results: []templates.CompetitionResult{ { - Year: 2019, - RecapParagraphs: []string{ - "The second ever CS@Mines High School Programming Competition featured 22 teams from all around Colorado and from as far as Steamboat Springs.", + Name: "Advanced", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Pen A Team", School: "PEN Academy", Location: "Cresskill, New Jersey"}, + {Place: "2nd", Name: "Cherry Creek Cobras", School: "Cherry Creek High School", Location: "Greenwood Village, Colorado"}, + {Place: "3nd", Name: "River Hill Team 1", School: "River Hill High School", Location: "Clarksville, Maryland"}, + {Place: "4th", Name: "The Spanish Inquisition", School: "Regis Jesuit High School", Location: "Aurora, Colorado"}, }, - Links: []Link{ - {"https://sumnerevans.com/posts/school/2019-hspc/", "Competition Recap and Solution Sketches"}, - {"https://mines19.kattis.com/problems", "Problems"}, + }, + { + Name: "Beginner", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "LLL", School: "Future Forward at Bollman", Location: "Thornton, Colorado"}, + {Place: "2nd", Name: "Error 404: Name not found", School: "Colorado Academy", Location: "Denver, Colorado"}, + {Place: "3nd", Name: "Liberty 1", School: "Liberty Common School", Location: "Fort Collins, Colorado"}, + {Place: "4th", Name: "Cool Cats", School: "Arvada West High School", Location: "Arvada, Colorado"}, }, - Results: []CompetitionResult{ - { - Teams: []WinningTeam{ - {"1st", "STEM Team 1", "STEM School Highlands Ranch", "Highlands Ranch, Colorado"}, - {"2nd", "IntrospectionExceptions", "Colorado Academy", "Lakewood, Colorado"}, - {"3nd", "Team 2", "?", "?"}, - }, - }, + }, + }, + }, + { + Year: 2021, + RecapParagraphs: []string{ + "The 2021 competition was an all-remote competition featuring 55 teams from across the nation.", + }, + Links: []templates.Link{ + {URL: "https://sumnerevans.com/posts/school/2021-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines21.kattis.com/problems", Title: "Problems"}, + }, + Results: []templates.CompetitionResult{ + { + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "River Hill HS Team 1", School: "River Hill High School", Location: "Clarksville, Maryland"}, + {Place: "2nd", Name: "PEN A Team", School: "PEN Academy", Location: "Cresskill, New Jersey"}, + {Place: "3nd", Name: "River Hill HS Team 2", School: "River Hill High School", Location: "Clarksville, Maryland"}, }, }, + }, + }, + { + Year: 2020, + RecapParagraphs: []string{ + "Due to COVID, the 2020 competition was the first all-remote HSPC competition. The competition featured 30 teams.", + }, + Links: []templates.Link{ + {URL: "https://sumnerevans.com/posts/school/2020-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines20.kattis.com/problems", Title: "Problems"}, + }, + Results: []templates.CompetitionResult{ { - Year: 2018, - RecapParagraphs: []string{ - "The first ever CS@Mines High School Programming Competition featured 22 teams.", + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "Installation Wizards", School: "STEM School Highlands Ranch", Location: "Highlands Ranch, Colorado"}, + {Place: "2nd", Name: "i", School: "STEM School Highlands Ranch", Location: "Highlands Ranch, Colorado"}, + {Place: "3nd", Name: "Sun Devils", School: "Kent Denver", Location: "Denver, Colorado"}, }, - Links: []Link{ - {"https://mines18.kattis.com/problems", "Problems"}, + }, + }, + }, + { + Year: 2019, + RecapParagraphs: []string{ + "The second ever CS@Mines High School Programming Competition featured 22 teams from all around Colorado and from as far as Steamboat Springs.", + }, + Links: []templates.Link{ + {URL: "https://sumnerevans.com/posts/school/2019-hspc/", Title: "Competition Recap and Solution Sketches"}, + {URL: "https://mines19.kattis.com/problems", Title: "Problems"}, + }, + Results: []templates.CompetitionResult{ + { + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "STEM Team 1", School: "STEM School Highlands Ranch", Location: "Highlands Ranch, Colorado"}, + {Place: "2nd", Name: "IntrospectionExceptions", School: "Colorado Academy", Location: "Lakewood, Colorado"}, + {Place: "3nd", Name: "Team 2", School: "?", Location: "?"}, }, - Results: []CompetitionResult{ - { - Teams: []WinningTeam{ - {"1st", "The Crummies", "Warren Tech", "Arvada, Colorado"}, - {"2nd", "The Bean Beans", "Colorado Academy", "Lakewood, Colorado"}, - {"3nd", "Warriors", "Arapahoe High School", "Centennial, Colorado"}, - }, - }, + }, + }, + }, + { + Year: 2018, + RecapParagraphs: []string{ + "The first ever CS@Mines High School Programming Competition featured 22 teams.", + }, + Links: []templates.Link{ + {URL: "https://mines18.kattis.com/problems", Title: "Problems"}, + }, + Results: []templates.CompetitionResult{ + { + Teams: []templates.WinningTeam{ + {Place: "1st", Name: "The Crummies", School: "Warren Tech", Location: "Arvada, Colorado"}, + {Place: "2nd", Name: "The Bean Beans", School: "Colorado Academy", Location: "Lakewood, Colorado"}, + {Place: "3nd", Name: "Warriors", School: "Arapahoe High School", Location: "Centennial, Colorado"}, }, }, }, - } + }, } diff --git a/internal/contextkeys/keys.go b/internal/contextkeys/keys.go new file mode 100644 index 0000000..2d31ace --- /dev/null +++ b/internal/contextkeys/keys.go @@ -0,0 +1,10 @@ +package contextkeys + +type contextKey int + +const ( + ContextKeyLoggedInTeacher contextKey = iota + ContextKeyPageName + ContextKeyRegistrationEnabled + ContextKeyHostedByHTML +) diff --git a/internal/templates/archive.templ b/internal/templates/archive.templ new file mode 100644 index 0000000..fb1316c --- /dev/null +++ b/internal/templates/archive.templ @@ -0,0 +1,145 @@ +package templates + +import "strconv" + +type Link struct { + URL templ.SafeURL + Title string +} + +type WinningTeam struct { + Place string + Name string + School string + Location string +} + +type CompetitionResult struct { + Name string + Shortname string + Teams []WinningTeam +} + +type YearInfo struct { + Year int + RecapParagraphs []string + Links []Link + Results []CompetitionResult +} + +func accordionHeadingID(year int, shortname string) string { + return "heading" + strconv.Itoa(year) + shortname +} + +func accordionCollapseCSSID(year int, shortname string) string { + return "#" + accordionCollapseID(year, shortname) +} + +func accordionCollapseID(year int, shortname string) string { + return "winners" + strconv.Itoa(year) + shortname +} + +func trophyColor(place int) string { + switch place + 1 { + case 1: + return "#FFD700" + case 2: + return "#C0C0C0" + default: + return "#CD7F32" + } +} + +css trophyColorStyle(place int) { + color: { trophyColor(place) }; +} + +templ Archive(years []YearInfo) { +
+ +
+
+ for _, y := range years { + @year(y) + } +
+} + +templ year(y YearInfo) { +
+
+

{ strconv.Itoa(y.Year) }

+ for _, p := range y.RecapParagraphs { +

{ p }

+ } +
+
+
+ for _, r := range y.Results { +
+

+ +

+
+
+
+ for i, t := range r.Teams { + @winningTeam(i, t) + } +
+
+ } +
+
+
+
+ for i, link := range y.Links { + if i > 0 { +  • + } + { link.Title } + } +
+
+
+} + +templ winningTeam(i int, team WinningTeam) { +
  • + + + { team.Place } + + { team.Name } +

    + { team.School } • { team.Location } +

    +
  • +} diff --git a/internal/templates/authors.templ b/internal/templates/authors.templ new file mode 100644 index 0000000..941d46d --- /dev/null +++ b/internal/templates/authors.templ @@ -0,0 +1,65 @@ +package templates + +templ Authors() { +
    + +
    +
    +
    +
    +

    Writing Exciting Problems

    +

    + The CS@Mines High School Programming Competition is a competition for high + school students to write programs that solve problems. We model our programming + competition off well-known college-level competitions such as the + ACM ICPC, but bring an inviting set of + problems to the table suitable for high school students. +

    +

    + Mines students are encouraged join our problem writing process. In the past, we + have had many authors from ACM, DECtech, and the CS@Mines department, as well + as help from alumni. All of our problems are reviewed by CS@Mines professors. +

    +

    + See the sections below to get involved! + Problems are due December 31st, 2024. +

    +

    The Problem Process

    +

    + An outline of the problem process can be seen below. Once you join our GitHub + organization, a full contribution guide is available. The bulk of the + problem-making processes lies in making a well-defined problem and writing a + solution and test cases for it. +

    +

    + +

    +

    + Anyone can write and review problems! In the past, we have typically always + needed extra reviewers. Along with writing your own problem, you can help us by + writing solutions to other problems to ensure the best quality problems. +

    +

    Get In Touch

    +

    + We use GitHub to store + and collaborate on all of the problem code for HSPC. Due to the nature of the + competition, this repository is private. +

    +

    + Please join our Discord server and + email erichards@mines.edu to get + involved in the problem writing process and to get access to the codebase. + Problem writers are also encouraged to volunteer the day of the competition. +

    +
    +
    +
    +} diff --git a/internal/templates/base.templ b/internal/templates/base.templ index e69de29..fed929c 100644 --- a/internal/templates/base.templ +++ b/internal/templates/base.templ @@ -0,0 +1,54 @@ +package templates + +import "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/templates/partials" + +templ Base(title string, content templ.Component) { + + + + + + + + { title } | CS@Mines High School Programming Competition + + + + + + + + + + + + + + + + @partials.Navbar() +
    + @content +
    + @partials.Footer() + + + +} diff --git a/internal/templates/faq.templ b/internal/templates/faq.templ new file mode 100644 index 0000000..31725da --- /dev/null +++ b/internal/templates/faq.templ @@ -0,0 +1,103 @@ +package templates + +templ FAQ() { +
    + +
    +
    +
    +
    +

    How should I prepare for the competition?

    +

    + The best way to practice is by solving the problems from previous + competitions. You can find this all on our archive page. + We will use Kattis to run the competition, so all the problems will be given in a similar + input/output format to those seen in the + Open Kattis Archive. +

    +

    How many people can be on my team?

    +

    + Teams must have a minimum of two students and a maximum of four students. +

    +

    How many teams can we have?

    +

    + We allow for two teams per school to attend our in-person competition + (we may allow more based on registration time and available space). + Any number of teams can join in the virtual open division. +

    +

    What languages can we use?

    +

    + You can use C++, Python, Java or JavaScript. +

    +

    + We recommend that everyone on your team know at least one of these languages in common. We + additionally recommend that teams practice submitting solutions on Kattis because the + input/output is done in a very specific manner that students may not be familiar with. +

    +

    + Ensure that your team knows how to take in Kattis input before the competition day; + see the Kattis input tutorial { `for` } more details. + We will also send out a link to a practice competition to practice submitting code. +

    +

    What difficulty of problems are there?

    +

    + Starting in the 2024 competition, we will only have one division for everyone. There will be 12-15 + problems, + and about a third will be "beginner", a third "novice", and a third "advanced" difficulty. +

    +

    + "Beginner" problem concepts include: arithmetic, expressions, conditionals, and lists. +

    +

    + "Novice" problems concepts include: loops, nested loops, lists, and maybe some data structures. +

    +

    + "Advanced" problems concepts include: data structures (maps, sets) and algorithms (sorting/BFS/DFS) +

    +

    + View + + last year's beginner + problems + and + + last year's advanced + problems + { `for` } a good idea of the range of difficulty for all of our problems. +

    +

    What editors do the competition computers have?

    +

    + The competition computers run on Ubuntu and have the following programs + installed: +

    +

    + IDEs: IntelliJ IDEA, + PyCharm, + Eclipse, + CLion. +

    +

    + Editors: Visual Studio Code, + IDLE, + emacs, + vim +

    +

    How long is the competition?

    +

    + The actual competition will be open for 4 hours, from 10:00 AM until 2:00 PM Mountain Time. + There are opening and closing ceremonies lasting an hour at the start and end of the competition. +

    +

    Can I bring my own device?

    +

    + Yes. However, we find that using less computers benefits team problem solving ability as + there is more opportunity for collaboration. + See the rules { `for` } details. +

    +
    +
    +
    +} diff --git a/internal/templates/home.templ b/internal/templates/home.templ new file mode 100644 index 0000000..3a972ab --- /dev/null +++ b/internal/templates/home.templ @@ -0,0 +1,156 @@ +package templates + +import ( + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/contextkeys" +) + +func RegistrationEnabled(ctx context.Context) bool { + enabled, ok := ctx.Value(contextkeys.ContextKeyRegistrationEnabled).(bool) + return ok && enabled +} + +templ Home() { +
    +
    + + + +
    +

    High School Programming Competition

    +

    Thanks for a great 2024 competition!

    + +

    + Engage in exciting problems at the Mines HSPC! + if RegistrationEnabled(ctx) { + Registration is now open for the Spring 2024 competition. + } +

    + if RegistrationEnabled(ctx) { +

    + + The registration deadline is April 13th +

    + + Register Now for In-Person Competition + + } else { +

    + + Registration for this year's competition has closed. + +

    + } + +
    +
    +
    +
    +
    +

    Engage in Exciting Problems

    +
    +

    + The CS@Mines High School Programming Competition is a competition for high school + students to write programs that solve problems. +

    +

    + Our programming competition is modelled after well-known college-level competitions + such as ICPC, but we make an inviting set of problems + suitable for high school students. +

    +

    + Starting in the 2024 competition, we will have three tracks, in the + form of in-person beginner and advanced competitions, and an open to + anyone, all difficulty virtual division. +

    +
    +
    +
    +
    + The opening ceremony at the 2024 competition +
    +
    +
    +
    + The closing ceremony at the 2024 competition +
    +
    +
    +

    Schedule

    +
    + // TODO change this to a table +

    Saturday, April 20th, 2024

    +
    +
    8:30 AM to 9:00 AM
    +
    Registration & Check-In
    +
    +
    +
    +
    9:00 AM to 10:00 AM
    +
    Opening Ceremony
    +
    +
    +
    +
    10:00 AM to 2:00 PM
    +
    Competition
    +
    +
    +
    +
    11:30 AM to 12:30 PM
    +
    Lunch available (at Mines)
    +
    +
    +
    +
    2:00 PM to 3:00 PM
    +
    Closing Ceremony & Awards
    +
    +
    +
    +
    3:00 PM to 4:00 PM
    +
    Optional Campus Tour
    +
    +
    +
    +
    +
    +
    +
    +
    +

    About Us

    +
    +

    + Mines is a public research university focused on science and engineering, located in Golden, + Colorado. + HSPC is organized and run by Mines ACM volunteers with funding from CS@Mines. +

    +

    + In the 2024 competition, we were pleased to host: +

      +
    • 18 different schools and 37 teams
    • +
    • 111 competitors
    • +
    • 15 teams consisting of first-time competitors
    • +
    • 74 first-time competitors
    • +
    • The largest number of problem authors (11)
    • +
    • The largest number of problems in a single competition (16)
    • +
    +

    +
    +
    +
    +
    + A picture of the Mines campus. +
    +
    +
    +} diff --git a/internal/templates/info.templ b/internal/templates/info.templ new file mode 100644 index 0000000..2a6433b --- /dev/null +++ b/internal/templates/info.templ @@ -0,0 +1,94 @@ +package templates + +templ Info() { +
    + +
    +
    +
    +
    +

    Address & Parking

    +

    + The competition will be at the Center for Technology and Learning Media (CTLM): + 1650 Arapahoe St, Golden, CO 80401 +

    +

    + We recommend parking at the CTLM Parking Lot on 18th St, and will have volunteers around the + building waiting. Parking is free since it is a weekend. Please arrive before 9:00am (ideally + 8:30am) so that you can check in. The competition ends at 3:00pm. +

    +

    + Once at CTLM, enter to the left of the “Gordian Knot,” and we will direct you to the opening + ceremony room, CTLM102. We will have volunteers and signs in case you get lost. +

    +

    + See the Mines campus map here. +

    +

    In-Person Check In

    +

    + Students will receive a QR code in their email which will act as their ticket for the competition. + A volunteer will scan the student's QR code to make sure all of the necessary forms have been + signed. +

    +

    Practice Competition

    +

    + We'll send out details via email a week before the competition describing how to test in the + practice competition. +

    +

    Food

    +

    + Food will be provided around 11:30am on competition day. +

    +

    Contact Information

    +

    + Please email support@mineshspc.com with any questions. +

    +

    Tips for Success

    +

    + Before the competition.. +

    +
      +
    • Get a good night's sleep.
    • +
    • Look at the problems from the last few years for an idea of what the competition is like.
    • +
    • Setup your development environment so that you can test your code locally.
    • +
    • + Discuss strategies with your team: who will work on what, who will do the typing, what types of + problems your team wants to start with, etc. +
    • +
    +

    + During the competition.. +

    +
      +
    • Carefully read the problems. Be sure to test your program on the sample inputs.
    • +
    • + Your solutions will be tested against a large number of test cases, many of which you cannot + see. +
    • +
    • + We recommend that you make up your own inputs to test your solution to make sure you have + handled all edge cases. +
    • +
    • Take a break or two to clear your mind. These problems are hard!
    • +
    • Do not blame each other. Stay positive in your interactions.
    • +
    • Don't stress out, and HAVE FUN!
    • +
    +

    + After the competition.. +

    +
      +
    • Congratulate yourselves. You accomplished something great.
    • +
    • + Do not be disheartened if you did not win. Use the experience as opportunity to grow as + programmer. +
    • +
    • If you had fun, try some of the other problems on Kattis and keep programming!
    • +
    +
    +
    +
    +} diff --git a/internal/templates/partials/footer.templ b/internal/templates/partials/footer.templ index db636fa..f96e4b7 100644 --- a/internal/templates/partials/footer.templ +++ b/internal/templates/partials/footer.templ @@ -1,6 +1,17 @@ package partials -templ Footer(hostedByHTML string) { +import ( + "html/template" + + "github.com/ColoradoSchoolOfMines/mineshspc.com/internal/contextkeys" +) + +func GetHostedByHTML(ctx context.Context) template.HTML { + hostedByHTML, _ := ctx.Value(contextkeys.ContextKeyHostedByHTML).(template.HTML) + return hostedByHTML +} + +templ Footer() {