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
|
||||
---------------------------------------------
|
||||
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
|
||||
|
|
|
@ -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> — {{ 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> — {{ 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 →</a>
|
||||
</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>
|
||||
{{ 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 }}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue