Fixed site ownership
This commit is contained in:
parent
cb54057305
commit
50f7e9632e
|
@ -12,7 +12,7 @@ exclude_file = []
|
|||
exclude_regex = ["_test.go", "build/.*"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = "export $(cat .env | xargs) ; cd build ; ./hugo-cms"
|
||||
full_bin = "export $(cat .env | xargs) ; make init-db ; cd build ; ./hugo-cms"
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html", "gohtml", "css", "js"]
|
||||
include_file = []
|
||||
|
|
5
Makefile
5
Makefile
|
@ -1,7 +1,7 @@
|
|||
.Phony: clean
|
||||
clean:
|
||||
-docker-compose down -v
|
||||
-rm -r build
|
||||
-rm -rf build
|
||||
|
||||
.Phony: prep
|
||||
prep:
|
||||
|
@ -14,5 +14,4 @@ compile: prep
|
|||
|
||||
.Phony: init-db
|
||||
init-db:
|
||||
export $(cat .env | xargs)
|
||||
./build/hugo-cms -user test@example.com -password test123
|
||||
go run . -user test@example.com -password test123
|
|
@ -160,7 +160,6 @@ type Site struct {
|
|||
OwnerUserID int64
|
||||
Name string
|
||||
Title string
|
||||
Url string
|
||||
Theme string
|
||||
Props []byte
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
const getSiteWithID = `-- name: GetSiteWithID :one
|
||||
SELECT id, owner_user_id, name, title, url, theme, props FROM sites WHERE id = $1 LIMIT 1
|
||||
SELECT id, owner_user_id, name, title, theme, props FROM sites WHERE id = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSiteWithID(ctx context.Context, id int64) (Site, error) {
|
||||
|
@ -21,7 +21,6 @@ func (q *Queries) GetSiteWithID(ctx context.Context, id int64) (Site, error) {
|
|||
&i.OwnerUserID,
|
||||
&i.Name,
|
||||
&i.Title,
|
||||
&i.Url,
|
||||
&i.Theme,
|
||||
&i.Props,
|
||||
)
|
||||
|
@ -29,7 +28,7 @@ func (q *Queries) GetSiteWithID(ctx context.Context, id int64) (Site, error) {
|
|||
}
|
||||
|
||||
const listSites = `-- name: ListSites :one
|
||||
SELECT id, owner_user_id, name, title, url, theme, props FROM sites
|
||||
SELECT id, owner_user_id, name, title, theme, props FROM sites
|
||||
`
|
||||
|
||||
func (q *Queries) ListSites(ctx context.Context) (Site, error) {
|
||||
|
@ -40,7 +39,6 @@ func (q *Queries) ListSites(ctx context.Context) (Site, error) {
|
|||
&i.OwnerUserID,
|
||||
&i.Name,
|
||||
&i.Title,
|
||||
&i.Url,
|
||||
&i.Theme,
|
||||
&i.Props,
|
||||
)
|
||||
|
@ -52,10 +50,9 @@ INSERT INTO sites (
|
|||
name,
|
||||
owner_user_id,
|
||||
title,
|
||||
url,
|
||||
theme,
|
||||
props
|
||||
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
|
@ -63,7 +60,6 @@ type NewSiteParams struct {
|
|||
Name string
|
||||
OwnerUserID int64
|
||||
Title string
|
||||
Url string
|
||||
Theme string
|
||||
Props []byte
|
||||
}
|
||||
|
@ -73,7 +69,6 @@ func (q *Queries) NewSite(ctx context.Context, arg NewSiteParams) (int64, error)
|
|||
arg.Name,
|
||||
arg.OwnerUserID,
|
||||
arg.Title,
|
||||
arg.Url,
|
||||
arg.Theme,
|
||||
arg.Props,
|
||||
)
|
||||
|
|
|
@ -9,6 +9,29 @@ import (
|
|||
"context"
|
||||
)
|
||||
|
||||
const getTargetOfSiteRole = `-- name: GetTargetOfSiteRole :one
|
||||
SELECT id, site_id, role, target_type, url, target_ref FROM publish_targets WHERE site_id = $1 AND role = $2 LIMIT 1
|
||||
`
|
||||
|
||||
type GetTargetOfSiteRoleParams struct {
|
||||
SiteID int64
|
||||
Role TargetRole
|
||||
}
|
||||
|
||||
func (q *Queries) GetTargetOfSiteRole(ctx context.Context, arg GetTargetOfSiteRoleParams) (PublishTarget, error) {
|
||||
row := q.db.QueryRow(ctx, getTargetOfSiteRole, arg.SiteID, arg.Role)
|
||||
var i PublishTarget
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SiteID,
|
||||
&i.Role,
|
||||
&i.TargetType,
|
||||
&i.Url,
|
||||
&i.TargetRef,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertPublishTarget = `-- name: InsertPublishTarget :one
|
||||
INSERT INTO publish_targets (
|
||||
site_id,
|
||||
|
@ -41,12 +64,12 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg
|
|||
return id, err
|
||||
}
|
||||
|
||||
const listPublishTargetsOfRole = `-- name: ListPublishTargetsOfRole :many
|
||||
SELECT id, site_id, role, target_type, url, target_ref FROM publish_targets WHERE site_id = $1 AND role = 'production'
|
||||
const listTargetsOfSite = `-- name: ListTargetsOfSite :many
|
||||
SELECT id, site_id, role, target_type, url, target_ref FROM publish_targets WHERE site_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListPublishTargetsOfRole(ctx context.Context, siteID int64) ([]PublishTarget, error) {
|
||||
rows, err := q.db.Query(ctx, listPublishTargetsOfRole, siteID)
|
||||
func (q *Queries) ListTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) {
|
||||
rows, err := q.db.Query(ctx, listTargetsOfSite, siteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"lmika.dev/lmika/hugo-cms/models"
|
||||
"log"
|
||||
)
|
||||
|
||||
func GetUser(c *fiber.Ctx) models.User {
|
||||
|
@ -21,3 +23,35 @@ func GetSite(c *fiber.Ctx) models.Site {
|
|||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func UpdatePrefCookie(c *fiber.Ctx, update func(prefs *models.PrefCookie)) {
|
||||
cookie := GetPrefCookie(c)
|
||||
update(&cookie)
|
||||
setPrefCookie(c, cookie)
|
||||
}
|
||||
|
||||
func GetPrefCookie(c *fiber.Ctx) models.PrefCookie {
|
||||
prefCookieValue := c.Cookies(models.PrefCookieName)
|
||||
if prefCookieValue == "" {
|
||||
return models.PrefCookie{}
|
||||
}
|
||||
|
||||
var prefCookie models.PrefCookie
|
||||
err := json.Unmarshal([]byte(prefCookieValue), &prefCookie)
|
||||
if err != nil {
|
||||
return models.PrefCookie{}
|
||||
}
|
||||
|
||||
return prefCookie
|
||||
}
|
||||
|
||||
func setPrefCookie(c *fiber.Ctx, prefCookie models.PrefCookie) {
|
||||
if prefJson, err := json.Marshal(prefCookie); err == nil {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: models.PrefCookieName,
|
||||
Value: string(prefJson),
|
||||
})
|
||||
} else {
|
||||
log.Printf("unable to save pref cookie: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
19
handlers/index.go
Normal file
19
handlers/index.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type IndexHandler struct {
|
||||
}
|
||||
|
||||
func (h IndexHandler) Index(c *fiber.Ctx) error {
|
||||
prefs := GetPrefCookie(c)
|
||||
if prefs.SiteID > 0 {
|
||||
return c.Redirect(fmt.Sprintf("/sites/%v/posts", prefs.SiteID), http.StatusFound)
|
||||
}
|
||||
|
||||
return c.Render("index", fiber.Map{}, "layouts/main")
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"lmika.dev/lmika/hugo-cms/models"
|
||||
"lmika.dev/lmika/hugo-cms/services/sites"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -19,6 +23,10 @@ func (s *Site) Create(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
UpdatePrefCookie(c, func(prefs *models.PrefCookie) {
|
||||
prefs.SiteID = site.ID
|
||||
})
|
||||
|
||||
return c.Redirect(fmt.Sprintf("/sites/%v/posts", site.ID))
|
||||
}
|
||||
|
||||
|
@ -47,7 +55,7 @@ func (s *Site) Rebuild(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func (s *Site) WithSite() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return func(c *fiber.Ctx) (err error) {
|
||||
id, err := c.ParamsInt("siteId")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -58,7 +66,19 @@ func (s *Site) WithSite() fiber.Handler {
|
|||
return err
|
||||
}
|
||||
|
||||
user := GetUser(c)
|
||||
if site.OwnerUserID != user.ID {
|
||||
return c.Status(http.StatusForbidden).SendString("not permitted")
|
||||
}
|
||||
|
||||
c.Locals("site", site)
|
||||
|
||||
if prodTarget, err := s.Site.GetProdTargetOfSite(c.UserContext(), int(site.ID)); err == nil {
|
||||
c.Locals("prodTarget", prodTarget)
|
||||
} else if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
|
5
main.go
5
main.go
|
@ -78,6 +78,7 @@ func main() {
|
|||
siteService := sites.NewService(cfg, dbp, themesProvider, siteBuilderService, jobService)
|
||||
postService := posts.New(dbp, siteBuilderService, jobService)
|
||||
|
||||
indexHandlers := handlers.IndexHandler{}
|
||||
siteHandlers := handlers.Site{Site: siteService}
|
||||
postHandlers := handlers.Post{Post: postService}
|
||||
authHandlers := handlers.AuthHandler{UserService: userService}
|
||||
|
@ -115,9 +116,7 @@ func main() {
|
|||
app.Post("/auth/login", authHandlers.Login)
|
||||
app.Use(authHandlers.RequireAuth)
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.Render("index", fiber.Map{}, "layouts/main")
|
||||
})
|
||||
app.Get("/", indexHandlers.Index)
|
||||
app.Post("/sites", siteHandlers.Create)
|
||||
app.Get("/sites/:siteId", siteHandlers.Show)
|
||||
|
||||
|
|
|
@ -2,8 +2,13 @@ package models
|
|||
|
||||
const (
|
||||
AuthCookieName = "hugocrm_auth"
|
||||
PrefCookieName = "hugocrm_pref"
|
||||
)
|
||||
|
||||
type AuthCookie struct {
|
||||
UserID int64 `json:"uid"`
|
||||
}
|
||||
|
||||
type PrefCookie struct {
|
||||
SiteID int64 `json:"siteId"`
|
||||
}
|
||||
|
|
|
@ -5,6 +5,5 @@ type Site struct {
|
|||
OwnerUserID int64
|
||||
Name string
|
||||
Title string
|
||||
URL string
|
||||
Theme string
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lmika.dev/lmika/hugo-cms/gen/sqlc/dbq"
|
||||
"lmika.dev/lmika/hugo-cms/models"
|
||||
"lmika.dev/pkg/modash/moslice"
|
||||
)
|
||||
|
||||
func (db *DB) InsertPublishTarget(ctx context.Context, target *models.PublishTarget) error {
|
||||
id, err := db.q.InsertPublishTarget(ctx, dbq.InsertPublishTargetParams{
|
||||
SiteID: target.SiteID,
|
||||
Role: dbq.TargetRole(target.Role),
|
||||
TargetType: dbq.TargetType(target.Type),
|
||||
Url: target.URL,
|
||||
TargetRef: target.TargetRef,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPublishTargets(ctx context.Context, siteID int64) ([]models.PublishTarget, error) {
|
||||
res, err := db.q.ListPublishTargetsOfRole(ctx, siteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return moslice.Map(res, func(m dbq.PublishTarget) models.PublishTarget {
|
||||
return models.PublishTarget{
|
||||
ID: m.ID,
|
||||
SiteID: m.SiteID,
|
||||
Role: models.TargetRole(m.Role),
|
||||
Type: models.TargetType(m.TargetType),
|
||||
URL: m.Url,
|
||||
TargetRef: m.TargetRef,
|
||||
}
|
||||
}), nil
|
||||
}
|
|
@ -11,7 +11,6 @@ func (db *DB) InsertSite(ctx context.Context, site *models.Site) error {
|
|||
Name: site.Name,
|
||||
Title: site.Title,
|
||||
OwnerUserID: site.OwnerUserID,
|
||||
Url: site.URL,
|
||||
Theme: site.Theme,
|
||||
Props: []byte("{}"),
|
||||
})
|
||||
|
@ -29,10 +28,10 @@ func (db *DB) GetSite(ctx context.Context, id int64) (models.Site, error) {
|
|||
}
|
||||
|
||||
return models.Site{
|
||||
ID: site.ID,
|
||||
Name: site.Name,
|
||||
Title: site.Title,
|
||||
URL: site.Url,
|
||||
Theme: site.Theme,
|
||||
ID: site.ID,
|
||||
OwnerUserID: site.OwnerUserID,
|
||||
Name: site.Name,
|
||||
Title: site.Title,
|
||||
Theme: site.Theme,
|
||||
}, nil
|
||||
}
|
||||
|
|
56
providers/db/targets.go
Normal file
56
providers/db/targets.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lmika.dev/lmika/hugo-cms/gen/sqlc/dbq"
|
||||
"lmika.dev/lmika/hugo-cms/models"
|
||||
"lmika.dev/pkg/modash/moslice"
|
||||
)
|
||||
|
||||
func (db *DB) InsertPublishTarget(ctx context.Context, target *models.PublishTarget) error {
|
||||
id, err := db.q.InsertPublishTarget(ctx, dbq.InsertPublishTargetParams{
|
||||
SiteID: target.SiteID,
|
||||
Role: dbq.TargetRole(target.Role),
|
||||
TargetType: dbq.TargetType(target.Type),
|
||||
Url: target.URL,
|
||||
TargetRef: target.TargetRef,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.ID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPublishTargets(ctx context.Context, siteID int64) ([]models.PublishTarget, error) {
|
||||
res, err := db.q.ListTargetsOfSite(ctx, siteID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return moslice.Map(res, dbTargetToTarget), nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPublishTargetBySiteRole(ctx context.Context, siteID int64, role models.TargetRole) (models.PublishTarget, error) {
|
||||
target, err := db.q.GetTargetOfSiteRole(ctx, dbq.GetTargetOfSiteRoleParams{
|
||||
SiteID: siteID,
|
||||
Role: dbq.TargetRole(role),
|
||||
})
|
||||
if err != nil {
|
||||
return models.PublishTarget{}, err
|
||||
}
|
||||
|
||||
return dbTargetToTarget(target), nil
|
||||
}
|
||||
|
||||
func dbTargetToTarget(m dbq.PublishTarget) models.PublishTarget {
|
||||
return models.PublishTarget{
|
||||
ID: m.ID,
|
||||
SiteID: m.SiteID,
|
||||
Role: models.TargetRole(m.Role),
|
||||
Type: models.TargetType(m.TargetType),
|
||||
URL: m.Url,
|
||||
TargetRef: m.TargetRef,
|
||||
}
|
||||
}
|
|
@ -41,13 +41,16 @@ func (s *Service) GetSite(ctx context.Context, id int) (models.Site, error) {
|
|||
return s.db.GetSite(ctx, int64(id))
|
||||
}
|
||||
|
||||
func (s *Service) GetProdTargetOfSite(ctx context.Context, siteID int) (models.PublishTarget, error) {
|
||||
return s.db.GetPublishTargetBySiteRole(ctx, int64(siteID), models.TargetRoleProduction)
|
||||
}
|
||||
|
||||
func (s *Service) CreateSite(ctx context.Context, user models.User, name string) (models.Site, error) {
|
||||
newSite := models.Site{
|
||||
Name: normaliseName(name),
|
||||
OwnerUserID: user.ID,
|
||||
Title: name,
|
||||
Theme: "bear",
|
||||
URL: "https://meek-meringue-060cfc.netlify.app/",
|
||||
}
|
||||
|
||||
_, ok := s.themes.Lookup(newSite.Theme)
|
||||
|
|
|
@ -9,8 +9,7 @@ INSERT INTO sites (
|
|||
name,
|
||||
owner_user_id,
|
||||
title,
|
||||
url,
|
||||
theme,
|
||||
props
|
||||
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id;
|
|
@ -1,5 +1,8 @@
|
|||
-- name: ListPublishTargetsOfRole :many
|
||||
SELECT * FROM publish_targets WHERE site_id = $1 AND role = 'production';
|
||||
-- name: ListTargetsOfSite :many
|
||||
SELECT * FROM publish_targets WHERE site_id = $1;
|
||||
|
||||
-- name: GetTargetOfSiteRole :one
|
||||
SELECT * FROM publish_targets WHERE site_id = $1 AND role = $2 LIMIT 1;
|
||||
|
||||
-- name: InsertPublishTarget :one
|
||||
INSERT INTO publish_targets (
|
||||
|
|
|
@ -22,7 +22,6 @@ CREATE TABLE sites (
|
|||
owner_user_id BIGINT NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
title TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
theme TEXT NOT NULL,
|
||||
props JSON NOT NULL,
|
||||
|
||||
|
@ -50,5 +49,6 @@ CREATE TABLE publish_targets (
|
|||
url TEXT NOT NULL,
|
||||
target_ref TEXT NOT NULL,
|
||||
|
||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE,
|
||||
UNIQUE (site_id, role)
|
||||
);
|
|
@ -1,5 +1,7 @@
|
|||
<h1>Thing</h1>
|
||||
|
||||
User = {{.user.Email}}
|
||||
|
||||
<form method="post" action="/sites">
|
||||
<input type="submit" value="Create Site">
|
||||
</form>
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
<h1>Hugo CMS</h1>
|
||||
<nav>
|
||||
<span>{{.site.Name}}</span>
|
||||
<a href="{{.site.URL}}">Visit</a>
|
||||
{{ if .prodTarget }}
|
||||
<a href="{{.prodTarget.URL}}" target="_blank">Visit</a>
|
||||
{{ end }}
|
||||
<a href="#">User</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
Loading…
Reference in a new issue