diff --git a/pkg/data/data.go b/pkg/data/data.go index c13b8c2..d3a5a1c 100644 --- a/pkg/data/data.go +++ b/pkg/data/data.go @@ -26,7 +26,9 @@ type Job struct { Description sql.NullString `db:"description" json:"-"` DescriptionJSON *string `db:"-" json:"description"` Email string `db:"email" json:"email"` - PublishedAt time.Time `db:"published_at" json:"published_at"` + Published bool `db:"published" json:"published"` + PublishedAt sql.NullTime `db:"published_at" json:"published_at"` + CreatedAt time.Time `db:"created_at" json:"created_at"` } const ( @@ -47,6 +49,10 @@ func (job *Job) Update(newParams NewJob) { job.Description.String = newParams.Description job.Description.Valid = newParams.Description != "" + + // Mark job posting as valid and publish + job.Published = true + job.PublishedAt.Time = time.Now() } func (job *Job) RenderDescription() (string, error) { @@ -75,8 +81,8 @@ func (job *Job) RenderDescription() (string, error) { func (job *Job) Save(db *sqlx.DB) (sql.Result, error) { return db.Exec( - "UPDATE jobs SET position = $1, organization = $2, url = $3, description = $4 WHERE id = $5", - job.Position, job.Organization, job.Url, job.Description, job.ID, + "UPDATE jobs SET position = $1, organization = $2, url = $3, description = $4, published = $5, published_at = $6 WHERE id = $7", + job.Position, job.Organization, job.Url, job.Description, job.Published, job.PublishedAt, job.ID, ) } @@ -85,7 +91,7 @@ func (job *Job) AuthSignature(secret string) string { "%s:%s:%s", job.ID, job.Email, - job.PublishedAt.String(), + job.CreatedAt.String(), ) hash := hmac.New(sha256.New, []byte(secret)) @@ -114,6 +120,26 @@ func GetAllJobs(db *sqlx.DB) ([]Job, error) { return jobs, nil } +func GetAllPublishedJobs(db *sqlx.DB) ([]Job, error) { + var jobs []Job + + err := db.Select(&jobs, "SELECT * FROM jobs WHERE published = true ORDER BY published_at DESC") + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return jobs, err + } + + for i := range jobs { + if !jobs[i].Url.Valid { + jobs[i].JSONUrl = &jobs[i].Url.String + } + if !jobs[i].Description.Valid { + jobs[i].DescriptionJSON = &jobs[i].Description.String + } + } + + return jobs, nil +} + func GetJob(id string, db *sqlx.DB) (Job, error) { var job Job @@ -132,19 +158,19 @@ func GetJob(id string, db *sqlx.DB) (Job, error) { return job, nil } -func DeleteJob(id string, db *sqlx.DB) (error) { - result, err := db.Exec("DELETE FROM jobs WHERE id = $1", id) - if err != nil { - return err - } - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected != 1 { - return fmt.Errorf("failed to delete job: %w", err) - } - return nil +func DeleteJob(id string, db *sqlx.DB) error { + result, err := db.Exec("DELETE FROM jobs WHERE id = $1", id) + if err != nil { + return err + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected != 1 { + return fmt.Errorf("failed to delete job: %w", err) + } + return nil } type NewJob struct { diff --git a/pkg/server/routes.go b/pkg/server/routes.go index bc061e1..611745e 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -23,7 +23,7 @@ type Controller struct { } func (ctrl *Controller) Index(ctx *gin.Context) { - jobs, err := data.GetAllJobs(ctrl.DB) + jobs, err := data.GetAllPublishedJobs(ctrl.DB) if err != nil { log.Println(fmt.Errorf("Index failed to getAllJobs: %w", err)) ctx.AbortWithStatus(http.StatusInternalServerError) @@ -123,7 +123,7 @@ func (ctrl *Controller) CreateJob(ctx *gin.Context) { if ctrl.EmailService != nil { // TODO: make this a nicer html template? - subject := fmt.Sprintf("Job %s Created!", newJobInput.Position) + subject := fmt.Sprintf("Job %s Created(draft)!", newJobInput.Position) message := fmt.Sprintf(` @@ -144,9 +144,10 @@ func (ctrl *Controller) CreateJob(ctx *gin.Context) {

Job %s Created!

-

Congratulations! Your job posting for the position %s has been successfully created.

-

You can edit or update your job posting by clicking the link below:

+

Congratulations! Your draft job posting for the position %s has been successfully created.

+

You can edit your job posting and publish it by clicking the link below:

Edit Job Posting

+

Note: Your job posting will not be live until you click Publish.

@@ -176,7 +177,7 @@ func (ctrl *Controller) CreateJob(ctx *gin.Context) { } } - session.AddFlash("Job created!") + session.AddFlash("Job created in draft status! Check email to publish.") ctx.Redirect(302, "/") } @@ -276,7 +277,6 @@ func (ctrl *Controller) DeleteJob(ctx *gin.Context) { ctx.Redirect(http.StatusFound, "/") } - func addFlash(ctx *gin.Context, base gin.H) gin.H { session := sessions.Default(ctx) base["flashes"] = session.Flashes() diff --git a/sql/20260201141821_update_jobs.down.sql b/sql/20260201141821_update_jobs.down.sql new file mode 100644 index 0000000..96bf8a9 --- /dev/null +++ b/sql/20260201141821_update_jobs.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE jobs + DROP COLUMN IF EXISTS published, + DROP COLUMN IF EXISTS published_at; +ALTER TABLE jobs RENAME created_at TO published_at; diff --git a/sql/20260201141821_update_jobs.up.sql b/sql/20260201141821_update_jobs.up.sql new file mode 100644 index 0000000..9ec13cb --- /dev/null +++ b/sql/20260201141821_update_jobs.up.sql @@ -0,0 +1,4 @@ +ALTER TABLE jobs RENAME published_at TO created_at; +ALTER TABLE jobs + ADD COLUMN IF NOT EXISTS published BOOLEAN DEFAULT false, + ADD COLUMN IF NOT EXISTS published_at TIMESTAMP DEFAULT null; diff --git a/templates/edit.html b/templates/edit.html index 2a38d9d..b4f3f7f 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -41,7 +41,7 @@
- +
diff --git a/templates/index.html b/templates/index.html index 978807f..d1974fb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,9 +9,11 @@

{{ .Position }}

href="/jobs/{{ .ID }}" class="relative z-10 text-gray-500 hover:underline focus:underline" > - + {{ if .PublishedAt.Valid }} + + {{end}}
{{ if .Url.Valid }}