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 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

View File

@ -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> &mdash; {{ timehtml (relativedate .Date) .Date }} <div class="details">
</div> <a class="user" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a> &mdash; {{ 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 &rarr;</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 &rarr;</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 }}

View File

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

View File

@ -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()

View File

@ -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,
) )

View File

@ -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,
) )

View File

@ -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,