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,13 +3,16 @@
 | 
			
		|||
{{ define "content" }}
 | 
			
		||||
<div class="optionbar">
 | 
			
		||||
    <div class="options">
 | 
			
		||||
        {{ 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 */}}
 | 
			
		||||
{{ if .Posts }}
 | 
			
		||||
    {{ range .Posts }}
 | 
			
		||||
        <div class="flex items-start ph3 pv3 background-even">
 | 
			
		||||
            <img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}">
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +33,14 @@
 | 
			
		|||
            </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">
 | 
			
		||||
        {{ 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,6 +27,8 @@ func BlogIndex(c *RequestContext) ResponseData {
 | 
			
		|||
		templates.BaseData
 | 
			
		||||
		Posts      []blogIndexEntry
 | 
			
		||||
		Pagination templates.Pagination
 | 
			
		||||
 | 
			
		||||
		CanCreatePost bool
 | 
			
		||||
		NewPostUrl    string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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,6 +137,8 @@ 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)),
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		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