Add versioned board canvas persistence and stable SEO slugs
This commit is contained in:
@@ -11,7 +11,7 @@ func (s *service) workspaces(w http.ResponseWriter, r *http.Request, project, ac
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
rows, err := s.db.QueryContext(r.Context(), `
|
||||
SELECT w.id::text,w.name,m.role,w.owner_id::text FROM workspaces w
|
||||
SELECT w.id::text,w.name,m.role,w.owner_id::text,w.slug FROM workspaces w
|
||||
JOIN workspace_members m ON (m.project,m.workspace_id)=(w.project,w.id)
|
||||
WHERE m.project=$1 AND m.account_id=$2 ORDER BY lower(w.name),w.id LIMIT 200
|
||||
`, project, account)
|
||||
@@ -21,11 +21,11 @@ WHERE m.project=$1 AND m.account_id=$2 ORDER BY lower(w.name),w.id LIMIT 200
|
||||
defer rows.Close()
|
||||
items := []response{}
|
||||
for rows.Next() {
|
||||
var id, name, role, owner string
|
||||
if err := rows.Scan(&id, &name, &role, &owner); err != nil {
|
||||
var id, name, role, owner, slug string
|
||||
if err := rows.Scan(&id, &name, &role, &owner, &slug); err != nil {
|
||||
return err
|
||||
}
|
||||
items = append(items, response{"id": id, "name": name, "role": role, "owner_id": owner})
|
||||
items = append(items, response{"id": id, "name": name, "role": role, "owner_id": owner, "slug": slug})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
@@ -33,13 +33,14 @@ WHERE m.project=$1 AND m.account_id=$2 ORDER BY lower(w.name),w.id LIMIT 200
|
||||
reply(w, 200, response{"items": items})
|
||||
case http.MethodPost:
|
||||
var input struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
Slug *string `json:"slug,omitempty"`
|
||||
}
|
||||
if !decode(w, r, &input) {
|
||||
return nil
|
||||
}
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
if input.Name == "" || len(input.Name) > 160 {
|
||||
if input.Name == "" || len(input.Name) > 160 || !validSlug(input.Slug) {
|
||||
reject(w, 400, "invalid_workspace")
|
||||
return nil
|
||||
}
|
||||
@@ -52,10 +53,20 @@ WHERE m.project=$1 AND m.account_id=$2 ORDER BY lower(w.name),w.id LIMIT 200
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var slug string
|
||||
if input.Slug != nil {
|
||||
_, err = tx.ExecContext(r.Context(), "UPDATE workspaces SET slug=$3 WHERE project=$1 AND id=$2", project, id, *input.Slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = tx.QueryRowContext(r.Context(), "SELECT slug FROM workspaces WHERE project=$1 AND id=$2", project, id).Scan(&slug); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
reply(w, 201, response{"id": id, "name": input.Name, "role": "admin", "owner_id": account})
|
||||
reply(w, 201, response{"id": id, "name": input.Name, "role": "admin", "owner_id": account, "slug": slug})
|
||||
default:
|
||||
reject(w, 405, "method_not_allowed")
|
||||
}
|
||||
@@ -104,11 +115,11 @@ func (s *service) workspace(w http.ResponseWriter, r *http.Request, project, acc
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
var name string
|
||||
if err := tx.QueryRowContext(r.Context(), "SELECT name FROM workspaces WHERE project=$1 AND id=$2", project, workspace).Scan(&name); err != nil {
|
||||
var name, slug string
|
||||
if err := tx.QueryRowContext(r.Context(), "SELECT name,slug FROM workspaces WHERE project=$1 AND id=$2", project, workspace).Scan(&name, &slug); err != nil {
|
||||
return err
|
||||
}
|
||||
reply(w, 200, response{"id": workspace, "name": name, "role": role, "owner_id": owner})
|
||||
reply(w, 200, response{"id": workspace, "name": name, "role": role, "owner_id": owner, "slug": slug})
|
||||
return nil
|
||||
case http.MethodPatch:
|
||||
if role != "admin" {
|
||||
@@ -116,17 +127,18 @@ func (s *service) workspace(w http.ResponseWriter, r *http.Request, project, acc
|
||||
return nil
|
||||
}
|
||||
var input struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
Slug *string `json:"slug,omitempty"`
|
||||
}
|
||||
if !decode(w, r, &input) {
|
||||
return nil
|
||||
}
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
if input.Name == "" || len(input.Name) > 160 {
|
||||
if input.Name == "" || len(input.Name) > 160 || !validSlug(input.Slug) {
|
||||
reject(w, 400, "invalid_workspace")
|
||||
return nil
|
||||
}
|
||||
_, err = tx.ExecContext(r.Context(), "UPDATE workspaces SET name=$3,updated_at=now() WHERE project=$1 AND id=$2", project, workspace, input.Name)
|
||||
_, err = tx.ExecContext(r.Context(), "UPDATE workspaces SET name=$3,slug=COALESCE($4,slug),updated_at=now() WHERE project=$1 AND id=$2", project, workspace, input.Name, input.Slug)
|
||||
case http.MethodDelete:
|
||||
if owner != account {
|
||||
reject(w, 403, "owner_required")
|
||||
|
||||
Reference in New Issue
Block a user