Added jam/project association.
This commit is contained in:
parent
359354f2aa
commit
9d1d249ec0
|
@ -0,0 +1,116 @@
|
|||
package hmndata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
)
|
||||
|
||||
type Jam struct {
|
||||
Name string
|
||||
Slug string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
var WRJ2021 = Jam{
|
||||
Name: "Wheel Reinvention Jam 2021",
|
||||
Slug: "WRJ2021",
|
||||
StartTime: time.Date(2021, 9, 27, 0, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2021, 10, 4, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
var WRJ2022 = Jam{
|
||||
Name: "Wheel Reinvention Jam 2022",
|
||||
Slug: "WRJ2022",
|
||||
// StartTime: time.Date(2022, 8, 15, 0, 0, 0, 0, time.UTC),
|
||||
StartTime: time.Date(2021, 8, 15, 0, 0, 0, 0, time.UTC),
|
||||
EndTime: time.Date(2022, 8, 22, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
var AllJams = []Jam{WRJ2021, WRJ2022}
|
||||
|
||||
func CurrentJam() *Jam {
|
||||
now := time.Now()
|
||||
for i, jam := range AllJams {
|
||||
if jam.StartTime.Before(now) && now.Before(jam.EndTime) {
|
||||
return &AllJams[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func JamBySlug(slug string) Jam {
|
||||
for _, jam := range AllJams {
|
||||
if jam.Slug == slug {
|
||||
return jam
|
||||
}
|
||||
}
|
||||
return Jam{Slug: slug}
|
||||
}
|
||||
|
||||
func FetchJamsForProject(ctx context.Context, dbConn db.ConnOrTx, user *models.User, projectId int) ([]*models.JamProject, error) {
|
||||
jamProjects, err := db.Query[models.JamProject](ctx, dbConn,
|
||||
`
|
||||
SELECT $columns
|
||||
FROM jam_project
|
||||
WHERE project_id = $1
|
||||
`,
|
||||
projectId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, oops.New(err, "failed to fetch jams for project")
|
||||
}
|
||||
|
||||
currentJam := CurrentJam()
|
||||
foundCurrent := false
|
||||
for i, _ := range jamProjects {
|
||||
jam := JamBySlug(jamProjects[i].JamSlug)
|
||||
jamProjects[i].JamName = jam.Name
|
||||
jamProjects[i].JamStartTime = jam.StartTime
|
||||
|
||||
if currentJam != nil && currentJam.Slug == jamProjects[i].JamSlug {
|
||||
foundCurrent = true
|
||||
}
|
||||
}
|
||||
if currentJam != nil && !foundCurrent {
|
||||
jamProjects = append(jamProjects, &models.JamProject{
|
||||
ProjectID: projectId,
|
||||
JamSlug: currentJam.Slug,
|
||||
Participating: false,
|
||||
JamName: currentJam.Name,
|
||||
JamStartTime: currentJam.StartTime,
|
||||
})
|
||||
}
|
||||
|
||||
if user != nil && user.IsStaff {
|
||||
for _, jam := range AllJams {
|
||||
found := false
|
||||
for _, jp := range jamProjects {
|
||||
if jp.JamSlug == jam.Slug {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
jamProjects = append(jamProjects, &models.JamProject{
|
||||
ProjectID: projectId,
|
||||
JamSlug: jam.Slug,
|
||||
Participating: false,
|
||||
JamName: jam.Name,
|
||||
JamStartTime: jam.StartTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(jamProjects, func(i, j int) bool {
|
||||
return jamProjects[i].JamStartTime.Before(jamProjects[j].JamStartTime)
|
||||
})
|
||||
|
||||
return jamProjects, nil
|
||||
}
|
|
@ -56,7 +56,26 @@ func BuildJamIndex() string {
|
|||
return Url("/jam", nil)
|
||||
}
|
||||
|
||||
var RegexJamIndex2021 = regexp.MustCompile("^/jam/2021")
|
||||
var RegexJamIndex2021 = regexp.MustCompile("^/jam/2021$")
|
||||
|
||||
func BuildJamIndex2021() string {
|
||||
defer CatchPanic()
|
||||
return Url("/jam/2021", nil)
|
||||
}
|
||||
|
||||
var RegexJamIndex2022 = regexp.MustCompile("^/jam/2022$")
|
||||
|
||||
func BuildJamIndex2022() string {
|
||||
defer CatchPanic()
|
||||
return Url("/jam/2022", nil)
|
||||
}
|
||||
|
||||
var RegexJamFeed2022 = regexp.MustCompile("^/jam/2022/feed$")
|
||||
|
||||
func BuildJamFeed2022() string {
|
||||
defer CatchPanic()
|
||||
return Url("/jam/2022/feed", nil)
|
||||
}
|
||||
|
||||
// QUESTION(ben): Can we change these routes?
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMigration(AddJamProjects{})
|
||||
}
|
||||
|
||||
type AddJamProjects struct{}
|
||||
|
||||
func (m AddJamProjects) Version() types.MigrationVersion {
|
||||
return types.MigrationVersion(time.Date(2022, 6, 18, 1, 3, 39, 0, time.UTC))
|
||||
}
|
||||
|
||||
func (m AddJamProjects) Name() string {
|
||||
return "AddJamProjects"
|
||||
}
|
||||
|
||||
func (m AddJamProjects) Description() string {
|
||||
return "Add jam and project association table"
|
||||
}
|
||||
|
||||
func (m AddJamProjects) Up(ctx context.Context, tx pgx.Tx) error {
|
||||
_, err := tx.Exec(ctx,
|
||||
`
|
||||
CREATE TABLE jam_project (
|
||||
project_id INT REFERENCES project (id) ON DELETE CASCADE,
|
||||
jam_slug VARCHAR(64) NOT NULL,
|
||||
participating BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
UNIQUE (project_id, jam_slug)
|
||||
);
|
||||
CREATE INDEX jam_project_jam_slug ON jam_project (jam_slug);
|
||||
CREATE INDEX jam_project_project_id ON jam_project (project_id);
|
||||
`,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m AddJamProjects) Down(ctx context.Context, tx pgx.Tx) error {
|
||||
_, err := tx.Exec(ctx,
|
||||
`
|
||||
DROP INDEX jam_project_jam_slug;
|
||||
DROP INDEX jam_project_project_id;
|
||||
DROP TABLE jam_project;
|
||||
`,
|
||||
)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type JamProject struct {
|
||||
ProjectID int `db:"project_id"`
|
||||
JamSlug string `db:"jam_slug"`
|
||||
Participating bool `db:"participating"`
|
||||
JamName string
|
||||
JamStartTime time.Time
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
{{ if .Editing }}
|
||||
<h1>Edit {{ .ProjectSettings.Name }}</h1>
|
||||
{{ else }}
|
||||
<h1>Create a new project</h1>
|
||||
<h1>Create a new {{ if .ProjectSettings.JamParticipation }}jam {{ end }}project</h1>
|
||||
{{ end }}
|
||||
<form id="project_form" class="tabbed edit-form" method="POST" enctype="multipart/form-data">
|
||||
{{ csrftoken .Session }}
|
||||
|
@ -81,6 +81,20 @@
|
|||
<div class="c--dim f7" id="tag-discord-info">If you have linked your Discord account, any #project-showcase messages with the tag "&<span id="tag-preview"></span>" will automatically be associated with this project.</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .ProjectSettings.JamParticipation }}
|
||||
<div class="edit-form-row">
|
||||
<div class="pt-input-ns">Jam Participation</div>
|
||||
</div>
|
||||
{{ range .ProjectSettings.JamParticipation }}
|
||||
<div class="edit-form-row">
|
||||
<div>{{ .JamName }}:</div>
|
||||
<div>
|
||||
<input id="jam_{{ .JamSlug }}" type="checkbox" name="jam_participation" value="{{ .JamSlug }}" {{ if .Participating }}checked{{ end }} />
|
||||
<label for="jam_{{ .JamSlug }}">Participating</label>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if and .Editing .User.IsStaff }}
|
||||
<div class="edit-form-row">
|
||||
<div class="pt-input-ns">Admin settings</div>
|
||||
|
|
|
@ -138,13 +138,14 @@ type Project struct {
|
|||
}
|
||||
|
||||
type ProjectSettings struct {
|
||||
Name string
|
||||
Slug string
|
||||
Hidden bool
|
||||
Featured bool
|
||||
Personal bool
|
||||
Lifecycle string
|
||||
Tag string
|
||||
Name string
|
||||
Slug string
|
||||
Hidden bool
|
||||
Featured bool
|
||||
Personal bool
|
||||
Lifecycle string
|
||||
Tag string
|
||||
JamParticipation []ProjectJamParticipation
|
||||
|
||||
Blurb string
|
||||
Description string
|
||||
|
@ -155,6 +156,12 @@ type ProjectSettings struct {
|
|||
DarkLogo string
|
||||
}
|
||||
|
||||
type ProjectJamParticipation struct {
|
||||
JamName string
|
||||
JamSlug string
|
||||
Participating bool
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int
|
||||
Username string
|
||||
|
|
|
@ -14,12 +14,13 @@ import (
|
|||
func JamIndex2022(c *RequestContext) ResponseData {
|
||||
var res ResponseData
|
||||
|
||||
jamStartTime := time.Date(2022, 8, 15, 0, 0, 0, 0, time.UTC)
|
||||
jamEndTime := time.Date(2022, 8, 22, 0, 0, 0, 0, time.UTC)
|
||||
daysUntilStart := daysUntil(jamStartTime)
|
||||
daysUntilEnd := daysUntil(jamEndTime)
|
||||
// If logged in, fetch jam project
|
||||
// Link to project page if found, otherwise link to project creation page with ?jam=1
|
||||
|
||||
baseData := getBaseDataAutocrumb(c, "Wheel Reinvention Jam 2022")
|
||||
daysUntilStart := daysUntil(hmndata.WRJ2022.StartTime)
|
||||
daysUntilEnd := daysUntil(hmndata.WRJ2022.EndTime)
|
||||
|
||||
baseData := getBaseDataAutocrumb(c, hmndata.WRJ2022.Name)
|
||||
baseData.OpenGraphItems = []templates.OpenGraphItem{
|
||||
{Property: "og:site_name", Value: "Handmade.Network"},
|
||||
{Property: "og:type", Value: "website"},
|
||||
|
@ -41,11 +42,18 @@ func JamIndex2022(c *RequestContext) ResponseData {
|
|||
return res
|
||||
}
|
||||
|
||||
func JamFeed2022(c *RequestContext) ResponseData {
|
||||
// List newly-created jam projects
|
||||
// list snippets from jam projects
|
||||
// list forum posts from jam project threads
|
||||
// timeline everything
|
||||
return FourOhFour(c)
|
||||
}
|
||||
|
||||
func JamIndex2021(c *RequestContext) ResponseData {
|
||||
var res ResponseData
|
||||
|
||||
jamStartTime := time.Date(2021, 9, 27, 0, 0, 0, 0, time.UTC)
|
||||
daysUntilJam := daysUntil(jamStartTime)
|
||||
daysUntilJam := daysUntil(hmndata.WRJ2021.StartTime)
|
||||
if daysUntilJam < 0 {
|
||||
daysUntilJam = 0
|
||||
}
|
||||
|
@ -79,7 +87,7 @@ func JamIndex2021(c *RequestContext) ResponseData {
|
|||
showcaseJson := templates.TimelineItemsToJSON(showcaseItems)
|
||||
c.Perf.EndBlock()
|
||||
|
||||
baseData := getBaseDataAutocrumb(c, "Wheel Reinvention Jam")
|
||||
baseData := getBaseDataAutocrumb(c, hmndata.WRJ2021.Name)
|
||||
baseData.OpenGraphItems = []templates.OpenGraphItem{
|
||||
{Property: "og:site_name", Value: "Handmade.Network"},
|
||||
{Property: "og:type", Value: "website"},
|
||||
|
|
|
@ -378,6 +378,21 @@ func ProjectNew(c *RequestContext) ResponseData {
|
|||
var project templates.ProjectSettings
|
||||
project.Owners = append(project.Owners, templates.UserToTemplate(c.CurrentUser, c.Theme))
|
||||
project.Personal = true
|
||||
|
||||
var currentJam *hmndata.Jam
|
||||
if c.Req.URL.Query().Has("jam") {
|
||||
currentJam = hmndata.CurrentJam()
|
||||
if currentJam != nil {
|
||||
project.JamParticipation = []templates.ProjectJamParticipation{
|
||||
templates.ProjectJamParticipation{
|
||||
JamName: currentJam.Name,
|
||||
JamSlug: currentJam.Slug,
|
||||
Participating: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var res ResponseData
|
||||
res.MustWriteTemplate("project_edit.html", ProjectEditData{
|
||||
BaseData: getBaseDataAutocrumb(c, "New Project"),
|
||||
|
@ -489,6 +504,13 @@ func ProjectEdit(c *RequestContext) ResponseData {
|
|||
}
|
||||
c.Perf.EndBlock()
|
||||
|
||||
c.Perf.StartBlock("SQL", "Fetching project jams")
|
||||
projectJams, err := hmndata.FetchJamsForProject(c.Context(), c.Conn, c.CurrentUser, p.Project.ID)
|
||||
if err != nil {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch jams for project"))
|
||||
}
|
||||
c.Perf.EndBlock()
|
||||
|
||||
lightLogoUrl := templates.ProjectLogoUrl(&p.Project, p.LogoLightAsset, p.LogoDarkAsset, "light")
|
||||
darkLogoUrl := templates.ProjectLogoUrl(&p.Project, p.LogoLightAsset, p.LogoDarkAsset, "dark")
|
||||
|
||||
|
@ -502,6 +524,15 @@ func ProjectEdit(c *RequestContext) ResponseData {
|
|||
|
||||
projectSettings.LinksText = LinksToText(projectLinks)
|
||||
|
||||
projectSettings.JamParticipation = make([]templates.ProjectJamParticipation, 0, len(projectJams))
|
||||
for _, jam := range projectJams {
|
||||
projectSettings.JamParticipation = append(projectSettings.JamParticipation, templates.ProjectJamParticipation{
|
||||
JamName: jam.JamName,
|
||||
JamSlug: jam.JamSlug,
|
||||
Participating: jam.Participating,
|
||||
})
|
||||
}
|
||||
|
||||
var res ResponseData
|
||||
res.MustWriteTemplate("project_edit.html", ProjectEditData{
|
||||
BaseData: getBaseDataAutocrumb(c, "Edit Project"),
|
||||
|
@ -553,18 +584,19 @@ func ProjectEditSubmit(c *RequestContext) ResponseData {
|
|||
}
|
||||
|
||||
type ProjectPayload struct {
|
||||
ProjectID int
|
||||
Name string
|
||||
Blurb string
|
||||
Links []ParsedLink
|
||||
Description string
|
||||
ParsedDescription string
|
||||
Lifecycle models.ProjectLifecycle
|
||||
Hidden bool
|
||||
OwnerUsernames []string
|
||||
LightLogo FormImage
|
||||
DarkLogo FormImage
|
||||
Tag string
|
||||
ProjectID int
|
||||
Name string
|
||||
Blurb string
|
||||
Links []ParsedLink
|
||||
Description string
|
||||
ParsedDescription string
|
||||
Lifecycle models.ProjectLifecycle
|
||||
Hidden bool
|
||||
OwnerUsernames []string
|
||||
LightLogo FormImage
|
||||
DarkLogo FormImage
|
||||
Tag string
|
||||
JamParticipationSlugs []string
|
||||
|
||||
Slug string
|
||||
Featured bool
|
||||
|
@ -647,21 +679,24 @@ func ParseProjectEditForm(c *RequestContext) ProjectEditFormResult {
|
|||
return res
|
||||
}
|
||||
|
||||
jamParticipationSlugs := c.Req.Form["jam_participation"]
|
||||
|
||||
res.Payload = ProjectPayload{
|
||||
Name: projectName,
|
||||
Blurb: shortDesc,
|
||||
Links: links,
|
||||
Description: description,
|
||||
ParsedDescription: parsedDescription,
|
||||
Lifecycle: lifecycle,
|
||||
Hidden: hidden,
|
||||
OwnerUsernames: owners,
|
||||
LightLogo: lightLogo,
|
||||
DarkLogo: darkLogo,
|
||||
Tag: tag,
|
||||
Slug: slug,
|
||||
Personal: !official,
|
||||
Featured: featured,
|
||||
Name: projectName,
|
||||
Blurb: shortDesc,
|
||||
Links: links,
|
||||
Description: description,
|
||||
ParsedDescription: parsedDescription,
|
||||
Lifecycle: lifecycle,
|
||||
Hidden: hidden,
|
||||
OwnerUsernames: owners,
|
||||
LightLogo: lightLogo,
|
||||
DarkLogo: darkLogo,
|
||||
Tag: tag,
|
||||
JamParticipationSlugs: jamParticipationSlugs,
|
||||
Slug: slug,
|
||||
Personal: !official,
|
||||
Featured: featured,
|
||||
}
|
||||
|
||||
return res
|
||||
|
@ -861,6 +896,70 @@ func updateProject(ctx context.Context, tx pgx.Tx, user *models.User, payload *P
|
|||
twitch.UserOrProjectLinksUpdated(twitchLoginsPreChange, twitchLoginsPostChange)
|
||||
}
|
||||
|
||||
// NOTE(asaf): Regular users can only edit the jam participation status of the current jam or
|
||||
// jams the project was previously a part of.
|
||||
var possibleJamSlugs []string
|
||||
if user.IsStaff {
|
||||
possibleJamSlugs = make([]string, 0, len(hmndata.AllJams))
|
||||
for _, jam := range hmndata.AllJams {
|
||||
possibleJamSlugs = append(possibleJamSlugs, jam.Slug)
|
||||
}
|
||||
} else {
|
||||
possibleJamSlugs, err = db.QueryScalar[string](ctx, tx,
|
||||
`
|
||||
SELECT jam_slug
|
||||
FROM jam_project
|
||||
WHERE project_id = $1
|
||||
`,
|
||||
payload.ProjectID,
|
||||
)
|
||||
if err != nil {
|
||||
return oops.New(err, "Failed to fetch jam participation for project")
|
||||
}
|
||||
currentJam := hmndata.CurrentJam()
|
||||
if currentJam != nil {
|
||||
possibleJamSlugs = append(possibleJamSlugs, currentJam.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx,
|
||||
`
|
||||
UPDATE jam_project
|
||||
SET participating = FALSE
|
||||
WHERE project_id = $1
|
||||
`,
|
||||
payload.ProjectID,
|
||||
)
|
||||
if err != nil {
|
||||
return oops.New(err, "Failed to remove jam participation for project")
|
||||
}
|
||||
|
||||
for _, jamSlug := range payload.JamParticipationSlugs {
|
||||
found := false
|
||||
for _, possibleSlug := range possibleJamSlugs {
|
||||
if possibleSlug == jamSlug {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
_, err = tx.Exec(ctx,
|
||||
`
|
||||
INSERT INTO jam_project (project_id, jam_slug, participating)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (project_id, jam_slug) DO UPDATE SET
|
||||
participating = EXCLUDED.participating
|
||||
`,
|
||||
payload.ProjectID,
|
||||
jamSlug,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return oops.New(err, "Failed to insert/update jam participation for project")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -168,6 +168,8 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool) ht
|
|||
hmnOnly.GET(hmnurl.RegexWhenIsIt, WhenIsIt)
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex, JamIndex2022)
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex2021, JamIndex2021)
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex2022, JamIndex2022)
|
||||
hmnOnly.GET(hmnurl.RegexJamFeed2022, JamFeed2022)
|
||||
|
||||
hmnOnly.GET(hmnurl.RegexOldHome, Index)
|
||||
|
||||
|
|
Loading…
Reference in New Issue