Add permission check to the New Post button
This commit is contained in:
parent
c3e067fa44
commit
038ee7e90e
|
@ -0,0 +1,66 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(DropAuthGroups{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DropAuthGroups struct{}
|
||||||
|
|
||||||
|
func (m DropAuthGroups) Version() types.MigrationVersion {
|
||||||
|
return types.MigrationVersion(time.Date(2021, 8, 3, 2, 14, 18, 0, time.UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DropAuthGroups) Name() string {
|
||||||
|
return "DropAuthGroups"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DropAuthGroups) Description() string {
|
||||||
|
return "Drop the auth groups table, and related tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DropAuthGroups) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
|
DROP TABLE handmade_user_projects;
|
||||||
|
CREATE TABLE handmade_user_projects (
|
||||||
|
user_id INT NOT NULL REFERENCES auth_user (id) ON DELETE CASCADE,
|
||||||
|
project_id INT NOT NULL REFERENCES handmade_project (id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (user_id, project_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO handmade_user_projects (user_id, project_id)
|
||||||
|
SELECT agroups.user_id, pg.project_id
|
||||||
|
FROM
|
||||||
|
handmade_project_groups AS pg
|
||||||
|
JOIN auth_group AS ag ON ag.id = pg.group_id
|
||||||
|
JOIN auth_user_groups AS agroups ON agroups.group_id = ag.id;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to recreate handmade_user_projects")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
DROP TABLE auth_group_permissions;
|
||||||
|
DROP TABLE auth_user_groups;
|
||||||
|
DROP TABLE handmade_project_groups;
|
||||||
|
|
||||||
|
DROP TABLE auth_group;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to drop group-related tables")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DropAuthGroups) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
panic("Implement me")
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
Clean this up once we get the website working
|
Clean this up once we get the website working
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
TODO: Questionable db tables that we inherited from Django:
|
TODO: Questionable db tables that we inherited from Django:
|
||||||
* auth_group
|
|
||||||
* auth_group_permissions
|
|
||||||
* auth_permission
|
* auth_permission
|
||||||
* auth_user_groups
|
|
||||||
* auth_user_user_permissions
|
* auth_user_user_permissions
|
||||||
* django_admin_log
|
* django_admin_log
|
||||||
* django_content_type
|
* django_content_type
|
||||||
|
|
|
@ -3,36 +3,44 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="optionbar">
|
<div class="optionbar">
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<a class="button" href="{{ .NewPostUrl }}"><span class="big pr1">+</span> Create Post</a>
|
{{ if .CanCreatePost }}
|
||||||
|
<a class="button" href="{{ .NewPostUrl }}"><span class="big pr1">+</span> Create Post</a>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{{ template "pagination.html" .Pagination }}
|
{{ template "pagination.html" .Pagination }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/* TODO: Breadcrumbs, or some other link back to the blog index */}}
|
{{/* TODO: Breadcrumbs, or some other link back to the blog index */}}
|
||||||
{{ range .Posts }}
|
{{ if .Posts }}
|
||||||
<div class="flex items-start ph3 pv3 background-even">
|
{{ range .Posts }}
|
||||||
<img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}">
|
<div class="flex items-start ph3 pv3 background-even">
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}">
|
||||||
<div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<div class="details">
|
<div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
|
||||||
<a class="user" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a> — {{ timehtml (relativedate .Date) .Date }}
|
<div class="details">
|
||||||
</div>
|
<a class="user" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a> — {{ timehtml (relativedate .Date) .Date }}
|
||||||
<div class="overflow-hidden mh-5 mt2 relative">
|
</div>
|
||||||
<div>
|
<div class="overflow-hidden mh-5 mt2 relative">
|
||||||
{{ .Content }}
|
<div>
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt2">
|
||||||
|
<a href="{{ .Url }}">Read More →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div>
|
|
||||||
</div>
|
|
||||||
<div class="mt2">
|
|
||||||
<a href="{{ .Url }}">Read More →</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<div class="c--dimmer i pa3">There are no blog posts for this project yet.</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="optionbar bottom">
|
<div class="optionbar bottom">
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<a class="button" href="{{ .NewPostUrl }}"><span class="big pr1">+</span> Create Post</a>
|
{{ if .CanCreatePost }}
|
||||||
|
<a class="button" href="{{ .NewPostUrl }}"><span class="big pr1">+</span> Create Post</a>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{{ template "pagination.html" .Pagination }}
|
{{ template "pagination.html" .Pagination }}
|
||||||
|
|
|
@ -27,7 +27,9 @@ func BlogIndex(c *RequestContext) ResponseData {
|
||||||
templates.BaseData
|
templates.BaseData
|
||||||
Posts []blogIndexEntry
|
Posts []blogIndexEntry
|
||||||
Pagination templates.Pagination
|
Pagination templates.Pagination
|
||||||
NewPostUrl string
|
|
||||||
|
CanCreatePost bool
|
||||||
|
NewPostUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
const postsPerPage = 5
|
const postsPerPage = 5
|
||||||
|
@ -105,6 +107,23 @@ func BlogIndex(c *RequestContext) ResponseData {
|
||||||
baseData := getBaseData(c)
|
baseData := getBaseData(c)
|
||||||
baseData.Title = fmt.Sprintf("%s Blog", c.CurrentProject.Name)
|
baseData.Title = fmt.Sprintf("%s Blog", c.CurrentProject.Name)
|
||||||
|
|
||||||
|
canCreate := false
|
||||||
|
if c.CurrentUser != nil {
|
||||||
|
isProjectOwner := false
|
||||||
|
owners, err := FetchProjectOwners(c, c.CurrentProject.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch project owners"))
|
||||||
|
}
|
||||||
|
for _, owner := range owners {
|
||||||
|
if owner.ID == c.CurrentUser.ID {
|
||||||
|
isProjectOwner = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canCreate = c.CurrentUser.IsStaff || isProjectOwner
|
||||||
|
}
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("blog_index.html", blogIndexData{
|
res.MustWriteTemplate("blog_index.html", blogIndexData{
|
||||||
BaseData: baseData,
|
BaseData: baseData,
|
||||||
|
@ -118,7 +137,9 @@ func BlogIndex(c *RequestContext) ResponseData {
|
||||||
PreviousUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page-1, numPages)),
|
PreviousUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page-1, numPages)),
|
||||||
NextUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page+1, numPages)),
|
NextUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, utils.IntClamp(1, page+1, numPages)),
|
||||||
},
|
},
|
||||||
NewPostUrl: hmnurl.BuildBlogNewThread(c.CurrentProject.Slug),
|
|
||||||
|
CanCreatePost: canCreate,
|
||||||
|
NewPostUrl: hmnurl.BuildBlogNewThread(c.CurrentProject.Slug),
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,32 +206,31 @@ func AtomFeed(c *RequestContext) ResponseData {
|
||||||
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects"))
|
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects"))
|
||||||
}
|
}
|
||||||
var projectIds []int
|
var projectIds []int
|
||||||
projectMap := make(map[int]*templates.Project)
|
projectMap := make(map[int]int) // map[project id]index in slice
|
||||||
for _, p := range projects.ToSlice() {
|
for _, p := range projects.ToSlice() {
|
||||||
project := p.(*projectResult).Project
|
project := p.(*projectResult).Project
|
||||||
templateProject := templates.ProjectToTemplate(&project, c.Theme)
|
templateProject := templates.ProjectToTemplate(&project, c.Theme)
|
||||||
templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN()
|
templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN()
|
||||||
|
|
||||||
projectIds = append(projectIds, project.ID)
|
projectIds = append(projectIds, project.ID)
|
||||||
projectMap[project.ID] = &templateProject
|
|
||||||
feedData.Projects = append(feedData.Projects, templateProject)
|
feedData.Projects = append(feedData.Projects, templateProject)
|
||||||
|
projectMap[project.ID] = len(feedData.Projects) - 1
|
||||||
}
|
}
|
||||||
c.Perf.EndBlock()
|
c.Perf.EndBlock()
|
||||||
|
|
||||||
c.Perf.StartBlock("SQL", "Fetching project owners")
|
c.Perf.StartBlock("SQL", "Fetching project owners")
|
||||||
type ownerResult struct {
|
type ownerResult struct {
|
||||||
User models.User `db:"auth_user"`
|
User models.User `db:"auth_user"`
|
||||||
ProjectID int `db:"project_groups.project_id"`
|
ProjectID int `db:"uproj.project_id"`
|
||||||
}
|
}
|
||||||
owners, err := db.Query(c.Context(), c.Conn, ownerResult{},
|
owners, err := db.Query(c.Context(), c.Conn, ownerResult{},
|
||||||
`
|
`
|
||||||
SELECT $columns
|
SELECT $columns
|
||||||
FROM
|
FROM
|
||||||
auth_user
|
handmade_user_projects AS uproj
|
||||||
INNER JOIN auth_user_groups AS user_groups ON auth_user.id = user_groups.user_id
|
JOIN auth_user ON uproj.user_id = auth_user.id
|
||||||
INNER JOIN handmade_project_groups AS project_groups ON user_groups.group_id = project_groups.group_id
|
|
||||||
WHERE
|
WHERE
|
||||||
project_groups.project_id = ANY($1)
|
uproj.project_id = ANY($1)
|
||||||
`,
|
`,
|
||||||
projectIds,
|
projectIds,
|
||||||
)
|
)
|
||||||
|
@ -240,7 +239,7 @@ func AtomFeed(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
for _, res := range owners.ToSlice() {
|
for _, res := range owners.ToSlice() {
|
||||||
owner := res.(*ownerResult)
|
owner := res.(*ownerResult)
|
||||||
templateProject := projectMap[owner.ProjectID]
|
templateProject := &feedData.Projects[projectMap[owner.ProjectID]]
|
||||||
templateProject.Owners = append(templateProject.Owners, templates.UserToTemplate(&owner.User, ""))
|
templateProject.Owners = append(templateProject.Owners, templates.UserToTemplate(&owner.User, ""))
|
||||||
}
|
}
|
||||||
c.Perf.EndBlock()
|
c.Perf.EndBlock()
|
||||||
|
|
|
@ -36,10 +36,9 @@ func FetchProjectOwners(c *RequestContext, projectId int) ([]*models.User, error
|
||||||
SELECT $columns
|
SELECT $columns
|
||||||
FROM
|
FROM
|
||||||
auth_user
|
auth_user
|
||||||
INNER JOIN auth_user_groups AS user_groups ON auth_user.id = user_groups.user_id
|
INNER JOIN handmade_user_projects AS uproj ON uproj.user_id = auth_user.id
|
||||||
INNER JOIN handmade_project_groups AS project_groups ON user_groups.group_id = project_groups.group_id
|
|
||||||
WHERE
|
WHERE
|
||||||
project_groups.project_id = $1
|
uproj.project_id = $1
|
||||||
`,
|
`,
|
||||||
projectId,
|
projectId,
|
||||||
)
|
)
|
||||||
|
|
|
@ -106,10 +106,9 @@ func ProjectIndex(c *RequestContext) ResponseData {
|
||||||
SELECT $columns
|
SELECT $columns
|
||||||
FROM
|
FROM
|
||||||
handmade_project AS project
|
handmade_project AS project
|
||||||
INNER JOIN handmade_project_groups AS project_groups ON project_groups.project_id = project.id
|
INNER JOIN handmade_user_projects AS uproj ON uproj.project_id = project.id
|
||||||
INNER JOIN auth_user_groups AS user_groups ON user_groups.group_id = project_groups.group_id
|
|
||||||
WHERE
|
WHERE
|
||||||
user_groups.user_id = $1
|
uproj.user_id = $1
|
||||||
`,
|
`,
|
||||||
c.CurrentUser.ID,
|
c.CurrentUser.ID,
|
||||||
)
|
)
|
||||||
|
|
|
@ -91,10 +91,9 @@ func UserProfile(c *RequestContext) ResponseData {
|
||||||
SELECT $columns
|
SELECT $columns
|
||||||
FROM
|
FROM
|
||||||
handmade_project AS project
|
handmade_project AS project
|
||||||
INNER JOIN handmade_project_groups AS project_groups ON project_groups.project_id = project.id
|
INNER JOIN handmade_user_projects AS uproj ON uproj.project_id = project.id
|
||||||
INNER JOIN auth_user_groups AS user_groups ON user_groups.group_id = project_groups.group_id
|
|
||||||
WHERE
|
WHERE
|
||||||
user_groups.user_id = $1
|
uproj.user_id = $1
|
||||||
AND ($2 OR (project.flags = 0 AND project.lifecycle = ANY ($3)))
|
AND ($2 OR (project.flags = 0 AND project.lifecycle = ANY ($3)))
|
||||||
`,
|
`,
|
||||||
profileUser.ID,
|
profileUser.ID,
|
||||||
|
|
Loading…
Reference in New Issue