Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ We want the website to be a great example of Handmade software on the web. We en

You will need the following software installed:

- Go 1.26 or newer: https://go.dev/
- Go 1.27 or newer: https://go.dev/

You can download Go directly from the website, or install it through major package managers. If you already have Go installed, but are unsure of the version, you can check by running `go version`.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module git.handmade.network/hmn/hmn

go 1.26
go 1.27

require (
github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817
Expand Down
70 changes: 70 additions & 0 deletions public/js/betaweek_2026_submit_project.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/admintools/adminevent.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func addJamNagCommand(postCommand *cobra.Command) {
os.Exit(1)
}

nags, err := website.NagUsersToCreateJamProjects(ctx, conn, &jam)
nags, err := website.NagUsersToCreateJamProjects(ctx, conn, jam)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/discord/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (bot *botInstance) createApplicationCommands(ctx context.Context) {
Type: ApplicationCommandTypeChatInput,
Name: SlashCommandHMHReplay,
Description: "Join the Handmade Hero Replay",
DMPermission: utils.P(false),
DMPermission: new(false),
Options: []ApplicationCommandOption{
{
Type: ApplicationCommandOptionTypeBoolean,
Expand All @@ -82,7 +82,7 @@ func (bot *botInstance) createApplicationCommands(ctx context.Context) {
Type: ApplicationCommandTypeChatInput,
Name: SlashCommandJoinJam,
Description: "Join an upcoming jam",
DMPermission: utils.P(false),
DMPermission: new(false),
}))
}

Expand Down
16 changes: 15 additions & 1 deletion src/hmndata/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
)

type Event struct {
StartTime, EndTime time.Time
StartTime, EndTime time.Time
BannerStartTime, BannerEndTime time.Time

Name string
Description string // NOTE(asaf): Used by opengraph
Image string // NOTE(ben): Used by opengraph
Slug string
UrlSlug string
Url string

IndexUrl string
TicketSuccessUrl string
Expand All @@ -27,6 +29,18 @@ func (ev Event) Within(t time.Time) bool {
return ev.WithinGrace(t, 0, 0)
}

func (ev Event) WithinBanner(t time.Time) bool {
// NOTE(ben): This is kind of jank, but basically we assume that any event
// without an explicit banner start and end time is a jam and we use these
// common jam grace period variables for it, which is dumb but idk. We should
// probably just fill in all the banner things on existing jams and delete
// these constants.
if ev.BannerStartTime.IsZero() {
return ev.WithinGrace(t, JamBannerGraceBefore, JamBannerGraceAfter)
}
return ev.BannerStartTime.Before(t) && t.Before(ev.BannerEndTime)
}

func (ev Event) WithinGrace(t time.Time, before, after time.Duration) bool {
return ev.StartTime.Add(-before).Before(t) && t.Before(ev.EndTime.Add(after))
}
Expand Down
75 changes: 58 additions & 17 deletions src/hmndata/jams.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import (

"git.handmade.network/hmn/hmn/src/config"
"git.handmade.network/hmn/hmn/src/db"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/utils"
)

const JamProjectCreateGracePeriod = 14 * 24 * time.Hour
const JamBannerGraceBefore = 30 * 24 * time.Hour
const JamBannerGraceAfter = 14 * 24 * time.Hour

type Jam struct {
Event
Expand All @@ -33,6 +36,7 @@ var WRJ2021 = Jam{
Name: "Wheel Reinvention Jam 2021",
Slug: "WRJ2021",
UrlSlug: "2021",
Url: hmnurl.BuildJamIndex2021(),
},
}

Expand All @@ -43,6 +47,7 @@ var WRJ2022 = Jam{
Name: "Wheel Reinvention Jam 2022",
Slug: "WRJ2022",
UrlSlug: "2022",
Url: hmnurl.BuildJamIndex2022(),
},
}

Expand All @@ -53,6 +58,7 @@ var VJ2023 = Jam{
Name: "Visibility Jam 2023",
Slug: "VJ2023",
UrlSlug: "visibility-2023",
Url: hmnurl.BuildJamIndex2023_Visibility(),
},
}

Expand All @@ -63,6 +69,7 @@ var WRJ2023 = Jam{
Name: "Wheel Reinvention Jam 2023",
Slug: "WRJ2023",
UrlSlug: "2023",
Url: hmnurl.BuildJamIndex2023(),
},
}

Expand All @@ -74,6 +81,7 @@ var LJ2024 = Jam{
Description: "A two-weekend jam where you dive deep into a topic, then share it with the rest of the community.",
Slug: "LJ2024",
UrlSlug: "learning-2024",
Url: hmnurl.BuildJamGenericIndex("learning-2024"),
},
TemplateName: "2024_lj",
ForceDark: true,
Expand All @@ -90,6 +98,7 @@ var VJ2024 = Jam{
Description: "See things in a new way. July 19 - 21.",
Slug: "VJ2024",
UrlSlug: "visibility-2024",
Url: hmnurl.BuildJamGenericIndex("visibility-2024"),
},
TemplateName: "2024_vj",
}
Expand All @@ -102,6 +111,7 @@ var WRJ2024 = Jam{
Description: "A one-week jam where we build software from scratch. September 23 - 29 on the Handmade Network.",
Slug: "WRJ2024",
UrlSlug: "wheel-reinvention-2024",
Url: hmnurl.BuildJamGenericIndex("wheel-reinvention-2024"),
},
TemplateName: "2024_wrj",
}
Expand All @@ -114,6 +124,7 @@ var XRay2025 = Jam{
Description: "A jam to find out how software works on the inside. June 9 - 15 on the Handmade Network.",
Slug: "XRay2025",
UrlSlug: "x-ray-2025",
Url: hmnurl.BuildJamGenericIndex("x-ray-2025"),
},
TemplateName: "2025_xray",
ForceDark: true,
Expand All @@ -127,6 +138,7 @@ var WRJ2025 = Jam{
Description: "A one-week jam to build software from scratch. September 22 - 28 on the Handmade Network.",
Slug: "WRJ2025",
UrlSlug: "wheel-reinvention-2025",
Url: hmnurl.BuildJamGenericIndex("wheel-reinvention-2025"),
},
TemplateName: "2025_wrj",
}
Expand All @@ -139,6 +151,7 @@ var Essentials2026 = Jam{
Description: "Build simple programs you would use every day, from scratch, by hand. April 13 - 19, 2026.",
Slug: "Essentials2026",
UrlSlug: "essentials",
Url: hmnurl.BuildJamGenericIndex("essentials"),
},

TemplateName: "2026_essentials",
Expand All @@ -152,26 +165,54 @@ var Essentials2026 = Jam{
NagTime: time.Date(2026, 4, 11, 14, 0, 0, 0, time.UTC),
}

var AllJams = []Jam{
WRJ2021,
WRJ2022,
VJ2023,
WRJ2023,
LJ2024,
VJ2024,
WRJ2024,
XRay2025,
WRJ2025,
Essentials2026,
var BetaWeek2026 = Event{
StartTime: time.Date(2026, 11, 16, 13, 0, 0, 0, time.UTC),
EndTime: time.Date(2026, 11, 23, 5, 0, 0, 0, time.UTC),
BannerStartTime: time.Date(2026, 9, 27, 0, 0, 0, 0, time.UTC),
BannerEndTime: time.Date(2026, 11, 23, 5, 0, 0, 0, time.UTC),

Name: "Beta Week",
Description: "A focused week of testing Handmade software. November 16-22, 2026.",
Slug: "BetaWeek2026",
// NOTE(ben): No URL slug for now, since it's not clear what the patterns will be.
// The URL is /betaweek/2026, for now.
Url: hmnurl.BuildBetaWeekIndex(),
}

var AllJams = []*Jam{
&WRJ2021,
&WRJ2022,
&VJ2023,
&WRJ2023,
&LJ2024,
&VJ2024,
&WRJ2024,
&XRay2025,
&WRJ2025,
&Essentials2026,
}

var AllEvents = []*Event{
&WRJ2021.Event,
&WRJ2022.Event,
&VJ2023.Event,
&WRJ2023.Event,
&LJ2024.Event,
&VJ2024.Event,
&WRJ2024.Event,
&XRay2025.Event,
&WRJ2025.Event,
&Essentials2026.Event,
&BetaWeek2026,
}

var LatestJam = Essentials2026 // NOTE(asaf): The /jam route will redirect here
var LatestJam *Jam = &Essentials2026 // NOTE(ben): This is used for Discord integration, e.g. slash command and #jam channel activity

func CurrentJam() *Jam {
now := time.Now()
for i, jam := range AllJams {
if jam.Event.Within(now) {
return &AllJams[i]
return AllJams[i]
}
}
return nil
Expand All @@ -181,7 +222,7 @@ func UpcomingJam(window time.Duration) *Jam {
now := time.Now()
for i, jam := range AllJams {
if jam.Event.WithinGrace(now, window, 0) {
return &AllJams[i]
return AllJams[i]
}
}
return nil
Expand All @@ -191,19 +232,19 @@ func RecentJam(window time.Duration) *Jam {
now := time.Now()
for i, jam := range AllJams {
if jam.Event.WithinGrace(now, 0, window) {
return &AllJams[i]
return AllJams[i]
}
}
return nil
}

func JamBySlug(slug string) (Jam, bool) {
func JamBySlug(slug string) (*Jam, bool) {
for _, jam := range AllJams {
if jam.Slug == slug {
return jam, true
}
}
return Jam{Event: Event{Slug: slug}}, false
return &Jam{Slug: slug}, false
}

func FetchJamsForProject(ctx context.Context, dbConn db.ConnOrTx, user *models.User, projectId int) ([]*models.JamProject, error) {
Expand Down
24 changes: 23 additions & 1 deletion src/hmnurl/hmnurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/utils"
// NOTE(ben): Avoid importing oops to keep wasm builds smaller (thanks zerolog)
)

Expand Down Expand Up @@ -101,11 +102,32 @@ func NormalizePath(path string) string {
return res
}

func URLMatchesRoute(url *url.URL, route regexp.Regexp) bool {
// NOTE(ben): Does NOT check the host, only the path.
func URLPathMatchesRoute(url *url.URL, route *regexp.Regexp) bool {
path := NormalizePath(url.Path)
return route.MatchString(path)
}

// NOTE(ben): Mirrors what routing does and matches a route regex, extracting
// and returning the URL params. Panics if the route does not match. Like
// [URLPathMatchesRoute], does not check the host.
func MatchURLPathAgainstRoute(url *url.URL, route *regexp.Regexp) map[string]string {
path := NormalizePath(url.Path)
match := route.FindStringSubmatch(path)
utils.Assert(len(match) > 0)

res := make(map[string]string)
subexpNames := route.SubexpNames()
for i, param := range match {
paramName := subexpNames[i]
if paramName == "" {
continue
}
res[paramName] = param
}
return res
}

// Takes a project URL and rewrites it using the current URL context. This can be used
// to convert a personal project URL to official and vice versa.
func (c *UrlContext) RewriteProjectUrl(u *url.URL) string {
Expand Down
Loading
Loading