Add permission check to the New Post button

This commit is contained in:
Ben Visness 2021-08-02 22:27:59 -05:00
parent c3e067fa44
commit 038ee7e90e
8 changed files with 128 additions and 40 deletions

View File

@ -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")
}

View File

@ -1,10 +1,7 @@
Clean this up once we get the website working
---------------------------------------------
TODO: Questionable db tables that we inherited from Django:
* auth_group
* auth_group_permissions
* auth_permission
* auth_user_groups
* auth_user_user_permissions
* django_admin_log
* django_content_type

View File

@ -3,36 +3,44 @@
{{ define "content" }}
<div class="optionbar">
<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 class="options">
{{ template "pagination.html" .Pagination }}
</div>
</div>
{{/* TODO: Breadcrumbs, or some other link back to the blog index */}}
{{ range .Posts }}
<div class="flex items-start ph3 pv3 background-even">
<img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}">
<div class="flex-grow-1 overflow-hidden">
<div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
<div class="details">
<a class="user" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a> &mdash; {{ timehtml (relativedate .Date) .Date }}
</div>
<div class="overflow-hidden mh-5 mt2 relative">
<div>
{{ .Content }}
{{ if .Posts }}
{{ range .Posts }}
<div class="flex items-start ph3 pv3 background-even">
<img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}">
<div class="flex-grow-1 overflow-hidden">
<div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
<div class="details">
<a class="user" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a> &mdash; {{ timehtml (relativedate .Date) .Date }}
</div>
<div class="overflow-hidden mh-5 mt2 relative">
<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 &rarr;</a>
</div>
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div>
</div>
<div class="mt2">
<a href="{{ .Url }}">Read More &rarr;</a>
</div>
</div>
</div>
{{ end }}
{{ else }}
<div class="c--dimmer i pa3">There are no blog posts for this project yet.</div>
{{ end }}
<div class="optionbar bottom">
<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 class="options">
{{ template "pagination.html" .Pagination }}

View File

@ -27,7 +27,9 @@ func BlogIndex(c *RequestContext) ResponseData {
templates.BaseData
Posts []blogIndexEntry
Pagination templates.Pagination
NewPostUrl string
CanCreatePost bool
NewPostUrl string
}
const postsPerPage = 5
@ -105,6 +107,23 @@ func BlogIndex(c *RequestContext) ResponseData {
baseData := getBaseData(c)
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
res.MustWriteTemplate("blog_index.html", blogIndexData{
BaseData: baseData,
@ -118,7 +137,9 @@ func BlogIndex(c *RequestContext) ResponseData {
PreviousUrl: 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)
return res
}

View File

@ -206,32 +206,31 @@ func AtomFeed(c *RequestContext) ResponseData {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch feed projects"))
}
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() {
project := p.(*projectResult).Project
templateProject := templates.ProjectToTemplate(&project, c.Theme)
templateProject.UUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte(templateProject.Url)).URN()
projectIds = append(projectIds, project.ID)
projectMap[project.ID] = &templateProject
feedData.Projects = append(feedData.Projects, templateProject)
projectMap[project.ID] = len(feedData.Projects) - 1
}
c.Perf.EndBlock()
c.Perf.StartBlock("SQL", "Fetching project owners")
type ownerResult struct {
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{},
`
SELECT $columns
FROM
auth_user
INNER JOIN auth_user_groups AS user_groups ON auth_user.id = user_groups.user_id
INNER JOIN handmade_project_groups AS project_groups ON user_groups.group_id = project_groups.group_id
handmade_user_projects AS uproj
JOIN auth_user ON uproj.user_id = auth_user.id
WHERE
project_groups.project_id = ANY($1)
uproj.project_id = ANY($1)
`,
projectIds,
)
@ -240,7 +239,7 @@ func AtomFeed(c *RequestContext) ResponseData {
}
for _, res := range owners.ToSlice() {
owner := res.(*ownerResult)
templateProject := projectMap[owner.ProjectID]
templateProject := &feedData.Projects[projectMap[owner.ProjectID]]
templateProject.Owners = append(templateProject.Owners, templates.UserToTemplate(&owner.User, ""))
}
c.Perf.EndBlock()

View File

@ -36,10 +36,9 @@ func FetchProjectOwners(c *RequestContext, projectId int) ([]*models.User, error
SELECT $columns
FROM
auth_user
INNER JOIN auth_user_groups AS user_groups ON auth_user.id = user_groups.user_id
INNER JOIN handmade_project_groups AS project_groups ON user_groups.group_id = project_groups.group_id
INNER JOIN handmade_user_projects AS uproj ON uproj.user_id = auth_user.id
WHERE
project_groups.project_id = $1
uproj.project_id = $1
`,
projectId,
)

View File

@ -106,10 +106,9 @@ func ProjectIndex(c *RequestContext) ResponseData {
SELECT $columns
FROM
handmade_project AS project
INNER JOIN handmade_project_groups AS project_groups ON project_groups.project_id = project.id
INNER JOIN auth_user_groups AS user_groups ON user_groups.group_id = project_groups.group_id
INNER JOIN handmade_user_projects AS uproj ON uproj.project_id = project.id
WHERE
user_groups.user_id = $1
uproj.user_id = $1
`,
c.CurrentUser.ID,
)

View File

@ -91,10 +91,9 @@ func UserProfile(c *RequestContext) ResponseData {
SELECT $columns
FROM
handmade_project AS project
INNER JOIN handmade_project_groups AS project_groups ON project_groups.project_id = project.id
INNER JOIN auth_user_groups AS user_groups ON user_groups.group_id = project_groups.group_id
INNER JOIN handmade_user_projects AS uproj ON uproj.project_id = project.id
WHERE
user_groups.user_id = $1
uproj.user_id = $1
AND ($2 OR (project.flags = 0 AND project.lifecycle = ANY ($3)))
`,
profileUser.ID,