feat: enforce user roles
enforces user admin role requirement for - creating / deleting / setting role for organization users - creating / deleting / setting role for project users - updating project name - deleting project hides action elements based on role for - admin console - team settings if team is only visible through project membership - add project tile if not team admin - project name text editor if not team / project admin - add redirect from team page if settings only visible through project membership - add redirect from admin console if not org admin role enforcement is handled on the api side through a custom GraphQL directive `hasRole`. on the client side, role information is fetched in the TopNavbar's `me` query and stored in the `UserContext`. there is a custom hook, `useCurrentUser`, that provides a user object with two functions, `isVisibile` & `isAdmin` which is used to check roles in order to render/hide relevant UI elements.
This commit is contained in:
committed by
Jordan Knott
parent
5dbdc20b36
commit
e64f6f8569
@ -16,9 +16,17 @@ const (
|
||||
InstallOnly = "install_only"
|
||||
)
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleAdmin Role = "admin"
|
||||
RoleMember Role = "member"
|
||||
)
|
||||
|
||||
type AccessTokenClaims struct {
|
||||
UserID string `json:"userId"`
|
||||
Restricted RestrictedMode `json:"restricted"`
|
||||
OrgRole Role `json:"orgRole"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
@ -39,11 +47,16 @@ func (r *ErrMalformedToken) Error() string {
|
||||
return "token is malformed"
|
||||
}
|
||||
|
||||
func NewAccessToken(userID string, restrictedMode RestrictedMode) (string, error) {
|
||||
func NewAccessToken(userID string, restrictedMode RestrictedMode, orgRole string) (string, error) {
|
||||
role := RoleMember
|
||||
if orgRole == "admin" {
|
||||
role = RoleAdmin
|
||||
}
|
||||
accessExpirationTime := time.Now().Add(5 * time.Second)
|
||||
accessClaims := &AccessTokenClaims{
|
||||
UserID: userID,
|
||||
Restricted: restrictedMode,
|
||||
OrgRole: role,
|
||||
StandardClaims: jwt.StandardClaims{ExpiresAt: accessExpirationTime.Unix()},
|
||||
}
|
||||
|
||||
@ -60,6 +73,7 @@ func NewAccessTokenCustomExpiration(userID string, dur time.Duration) (string, e
|
||||
accessClaims := &AccessTokenClaims{
|
||||
UserID: userID,
|
||||
Restricted: Unrestricted,
|
||||
OrgRole: RoleMember,
|
||||
StandardClaims: jwt.StandardClaims{ExpiresAt: accessExpirationTime.Unix()},
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,6 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
func Execute() {
|
||||
rootCmd.SetVersionTemplate(versionTemplate)
|
||||
rootCmd.AddCommand(newWebCmd(), newMigrateCmd())
|
||||
rootCmd.AddCommand(newWebCmd(), newMigrateCmd(), newTokenCmd())
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
@ -10,7 +12,6 @@ import (
|
||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/migrations"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -27,6 +28,12 @@ func (l *MigrateLog) Verbose() bool {
|
||||
return l.verbose
|
||||
}
|
||||
|
||||
var migration http.FileSystem
|
||||
|
||||
func init() {
|
||||
migration = http.Dir("./migrations")
|
||||
}
|
||||
|
||||
func newMigrateCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "migrate",
|
||||
@ -53,7 +60,7 @@ func newMigrateCmd() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := httpfs.New(migrations.Migrations, "./")
|
||||
src, err := httpfs.New(migration, "./")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
13
internal/commands/migrate_prod.go
Normal file
13
internal/commands/migrate_prod.go
Normal file
@ -0,0 +1,13 @@
|
||||
// +build prod
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migration = migrations.Migrations
|
||||
}
|
27
internal/commands/token.go
Normal file
27
internal/commands/token.go
Normal file
@ -0,0 +1,27 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newTokenCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "token",
|
||||
Short: "Create a long lived JWT token for dev purposes",
|
||||
Long: "Create a long lived JWT token for dev purposes",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
token, err := auth.NewAccessTokenCustomExpiration(args[0], time.Hour*24)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("issue while creating access token")
|
||||
return
|
||||
}
|
||||
fmt.Println(token)
|
||||
},
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ type Project struct {
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Name string `json:"name"`
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
}
|
||||
|
||||
type ProjectLabel struct {
|
||||
@ -120,7 +119,6 @@ type Team struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Name string `json:"name"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
}
|
||||
|
||||
type TeamMember struct {
|
||||
|
@ -11,30 +11,23 @@ import (
|
||||
)
|
||||
|
||||
const createProject = `-- name: CreateProject :one
|
||||
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING project_id, team_id, created_at, name, owner
|
||||
INSERT INTO project(team_id, created_at, name) VALUES ($1, $2, $3) RETURNING project_id, team_id, created_at, name
|
||||
`
|
||||
|
||||
type CreateProjectParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) {
|
||||
row := q.db.QueryRowContext(ctx, createProject,
|
||||
arg.Owner,
|
||||
arg.TeamID,
|
||||
arg.CreatedAt,
|
||||
arg.Name,
|
||||
)
|
||||
row := q.db.QueryRowContext(ctx, createProject, arg.TeamID, arg.CreatedAt, arg.Name)
|
||||
var i Project
|
||||
err := row.Scan(
|
||||
&i.ProjectID,
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -93,7 +86,7 @@ func (q *Queries) DeleteProjectMember(ctx context.Context, arg DeleteProjectMemb
|
||||
}
|
||||
|
||||
const getAllProjects = `-- name: GetAllProjects :many
|
||||
SELECT project_id, team_id, created_at, name, owner FROM project
|
||||
SELECT project_id, team_id, created_at, name FROM project
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
|
||||
@ -110,7 +103,6 @@ func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -126,7 +118,7 @@ func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
|
||||
}
|
||||
|
||||
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
|
||||
SELECT project_id, team_id, created_at, name, owner FROM project WHERE team_id = $1
|
||||
SELECT project_id, team_id, created_at, name FROM project WHERE team_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
|
||||
@ -143,7 +135,39 @@ func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) (
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getAllVisibleProjectsForUserID = `-- name: GetAllVisibleProjectsForUserID :many
|
||||
SELECT project.project_id, project.team_id, project.created_at, project.name FROM project LEFT JOIN
|
||||
project_member ON project_member.project_id = project.project_id WHERE project_member.user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllVisibleProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAllVisibleProjectsForUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Project
|
||||
for rows.Next() {
|
||||
var i Project
|
||||
if err := rows.Scan(
|
||||
&i.ProjectID,
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -185,73 +209,8 @@ func (q *Queries) GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getOwnedProjectsForUserID = `-- name: GetOwnedProjectsForUserID :many
|
||||
SELECT project_id, team_id, created_at, name, owner FROM project WHERE owner = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetOwnedProjectsForUserID(ctx context.Context, owner uuid.UUID) ([]Project, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getOwnedProjectsForUserID, owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Project
|
||||
for rows.Next() {
|
||||
var i Project
|
||||
if err := rows.Scan(
|
||||
&i.ProjectID,
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getOwnedTeamProjectsForUserID = `-- name: GetOwnedTeamProjectsForUserID :many
|
||||
SELECT project_id FROM project WHERE owner = $1 AND team_id = $2
|
||||
`
|
||||
|
||||
type GetOwnedTeamProjectsForUserIDParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetOwnedTeamProjectsForUserID(ctx context.Context, arg GetOwnedTeamProjectsForUserIDParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getOwnedTeamProjectsForUserID, arg.Owner, arg.TeamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
for rows.Next() {
|
||||
var project_id uuid.UUID
|
||||
if err := rows.Scan(&project_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, project_id)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getProjectByID = `-- name: GetProjectByID :one
|
||||
SELECT project_id, team_id, created_at, name, owner FROM project WHERE project_id = $1
|
||||
SELECT project_id, team_id, created_at, name FROM project WHERE project_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) {
|
||||
@ -262,7 +221,6 @@ func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Proj
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -300,8 +258,40 @@ func (q *Queries) GetProjectMembersForProjectID(ctx context.Context, projectID u
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getProjectRolesForUserID = `-- name: GetProjectRolesForUserID :many
|
||||
SELECT project_id, role_code FROM project_member WHERE user_id = $1
|
||||
`
|
||||
|
||||
type GetProjectRolesForUserIDRow struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetProjectRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetProjectRolesForUserIDRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getProjectRolesForUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetProjectRolesForUserIDRow
|
||||
for rows.Next() {
|
||||
var i GetProjectRolesForUserIDRow
|
||||
if err := rows.Scan(&i.ProjectID, &i.RoleCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRoleForProjectMemberByUserID = `-- name: GetRoleForProjectMemberByUserID :one
|
||||
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
|
||||
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
|
||||
WHERE user_id = $1 AND project_id = $2
|
||||
`
|
||||
|
||||
@ -317,25 +307,29 @@ func (q *Queries) GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRo
|
||||
return i, err
|
||||
}
|
||||
|
||||
const setProjectOwner = `-- name: SetProjectOwner :one
|
||||
UPDATE project SET owner = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
|
||||
const getUserRolesForProject = `-- name: GetUserRolesForProject :one
|
||||
SELECT p.team_id, COALESCE(tm.role_code, '') AS team_role, COALESCE(pm.role_code, '') AS project_role
|
||||
FROM project AS p
|
||||
LEFT JOIN project_member AS pm ON pm.project_id = p.project_id AND pm.user_id = $1
|
||||
LEFT JOIN team_member AS tm ON tm.team_id = p.team_id AND tm.user_id = $1
|
||||
WHERE p.project_id = $2
|
||||
`
|
||||
|
||||
type SetProjectOwnerParams struct {
|
||||
type GetUserRolesForProjectParams struct {
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
}
|
||||
|
||||
func (q *Queries) SetProjectOwner(ctx context.Context, arg SetProjectOwnerParams) (Project, error) {
|
||||
row := q.db.QueryRowContext(ctx, setProjectOwner, arg.ProjectID, arg.Owner)
|
||||
var i Project
|
||||
err := row.Scan(
|
||||
&i.ProjectID,
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
)
|
||||
type GetUserRolesForProjectRow struct {
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
TeamRole string `json:"team_role"`
|
||||
ProjectRole string `json:"project_role"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserRolesForProject(ctx context.Context, arg GetUserRolesForProjectParams) (GetUserRolesForProjectRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUserRolesForProject, arg.UserID, arg.ProjectID)
|
||||
var i GetUserRolesForProjectRow
|
||||
err := row.Scan(&i.TeamID, &i.TeamRole, &i.ProjectRole)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@ -364,7 +358,7 @@ func (q *Queries) UpdateProjectMemberRole(ctx context.Context, arg UpdateProject
|
||||
}
|
||||
|
||||
const updateProjectNameByID = `-- name: UpdateProjectNameByID :one
|
||||
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
|
||||
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name
|
||||
`
|
||||
|
||||
type UpdateProjectNameByIDParams struct {
|
||||
@ -380,39 +374,6 @@ func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNa
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateProjectOwnerByOwnerID = `-- name: UpdateProjectOwnerByOwnerID :many
|
||||
UPDATE project SET owner = $2 WHERE owner = $1 RETURNING project_id
|
||||
`
|
||||
|
||||
type UpdateProjectOwnerByOwnerIDParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
Owner_2 uuid.UUID `json:"owner_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateProjectOwnerByOwnerID(ctx context.Context, arg UpdateProjectOwnerByOwnerIDParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, updateProjectOwnerByOwnerID, arg.Owner, arg.Owner_2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
for rows.Next() {
|
||||
var project_id uuid.UUID
|
||||
if err := rows.Scan(&project_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, project_id)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
@ -49,19 +49,18 @@ type Querier interface {
|
||||
GetAllTasks(ctx context.Context) ([]Task, error)
|
||||
GetAllTeams(ctx context.Context) ([]Team, error)
|
||||
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
||||
GetAllVisibleProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error)
|
||||
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
|
||||
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
|
||||
GetLabelColors(ctx context.Context) ([]LabelColor, error)
|
||||
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
||||
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
||||
GetOwnedProjectsForUserID(ctx context.Context, owner uuid.UUID) ([]Project, error)
|
||||
GetOwnedTeamProjectsForUserID(ctx context.Context, arg GetOwnedTeamProjectsForUserIDParams) ([]uuid.UUID, error)
|
||||
GetOwnedTeamsForUserID(ctx context.Context, owner uuid.UUID) ([]Team, error)
|
||||
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
|
||||
GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error)
|
||||
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)
|
||||
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
|
||||
GetProjectMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectMember, error)
|
||||
GetProjectRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetProjectRolesForUserIDRow, error)
|
||||
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
|
||||
GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRoleForProjectMemberByUserIDParams) (Role, error)
|
||||
GetRoleForTeamMember(ctx context.Context, arg GetRoleForTeamMemberParams) (Role, error)
|
||||
@ -81,21 +80,22 @@ type Querier interface {
|
||||
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
|
||||
GetTeamMemberByID(ctx context.Context, arg GetTeamMemberByIDParams) (TeamMember, error)
|
||||
GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error)
|
||||
GetTeamRoleForUserID(ctx context.Context, arg GetTeamRoleForUserIDParams) (GetTeamRoleForUserIDRow, error)
|
||||
GetTeamRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetTeamRolesForUserIDRow, error)
|
||||
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
|
||||
GetTeamsForUserIDWhereAdmin(ctx context.Context, userID uuid.UUID) ([]Team, error)
|
||||
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
|
||||
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
|
||||
SetProjectOwner(ctx context.Context, arg SetProjectOwnerParams) (Project, error)
|
||||
GetUserRolesForProject(ctx context.Context, arg GetUserRolesForProjectParams) (GetUserRolesForProjectRow, error)
|
||||
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
|
||||
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
|
||||
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
||||
SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error)
|
||||
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
|
||||
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
||||
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
||||
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error)
|
||||
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
|
||||
UpdateProjectOwnerByOwnerID(ctx context.Context, arg UpdateProjectOwnerByOwnerIDParams) ([]uuid.UUID, error)
|
||||
UpdateTaskChecklistItemLocation(ctx context.Context, arg UpdateTaskChecklistItemLocationParams) (TaskChecklistItem, error)
|
||||
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
|
||||
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
|
||||
@ -106,7 +106,6 @@ type Querier interface {
|
||||
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
|
||||
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
|
||||
UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
|
||||
UpdateTeamOwnerByOwnerID(ctx context.Context, arg UpdateTeamOwnerByOwnerIDParams) ([]uuid.UUID, error)
|
||||
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
|
||||
UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
|
||||
}
|
||||
|
@ -8,10 +8,7 @@ SELECT * FROM project WHERE team_id = $1;
|
||||
SELECT * FROM project WHERE project_id = $1;
|
||||
|
||||
-- name: CreateProject :one
|
||||
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *;
|
||||
|
||||
-- name: SetProjectOwner :one
|
||||
UPDATE project SET owner = $2 WHERE project_id = $1 RETURNING *;
|
||||
INSERT INTO project(team_id, created_at, name) VALUES ($1, $2, $3) RETURNING *;
|
||||
|
||||
-- name: UpdateProjectNameByID :one
|
||||
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *;
|
||||
@ -23,7 +20,7 @@ DELETE FROM project WHERE project_id = $1;
|
||||
SELECT * FROM project_member WHERE project_id = $1;
|
||||
|
||||
-- name: GetRoleForProjectMemberByUserID :one
|
||||
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
|
||||
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
|
||||
WHERE user_id = $1 AND project_id = $2;
|
||||
|
||||
-- name: CreateProjectMember :one
|
||||
@ -37,14 +34,19 @@ DELETE FROM project_member WHERE user_id = $1 AND project_id = $2;
|
||||
UPDATE project_member SET role_code = $3 WHERE project_id = $1 AND user_id = $2
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetOwnedTeamProjectsForUserID :many
|
||||
SELECT project_id FROM project WHERE owner = $1 AND team_id = $2;
|
||||
|
||||
-- name: GetOwnedProjectsForUserID :many
|
||||
SELECT * FROM project WHERE owner = $1;
|
||||
-- name: GetProjectRolesForUserID :many
|
||||
SELECT project_id, role_code FROM project_member WHERE user_id = $1;
|
||||
|
||||
-- name: GetMemberProjectIDsForUserID :many
|
||||
SELECT project_id FROM project_member WHERE user_id = $1;
|
||||
|
||||
-- name: UpdateProjectOwnerByOwnerID :many
|
||||
UPDATE project SET owner = $2 WHERE owner = $1 RETURNING project_id;
|
||||
-- name: GetAllVisibleProjectsForUserID :many
|
||||
SELECT project.* FROM project LEFT JOIN
|
||||
project_member ON project_member.project_id = project.project_id WHERE project_member.user_id = $1;
|
||||
|
||||
-- name: GetUserRolesForProject :one
|
||||
SELECT p.team_id, COALESCE(tm.role_code, '') AS team_role, COALESCE(pm.role_code, '') AS project_role
|
||||
FROM project AS p
|
||||
LEFT JOIN project_member AS pm ON pm.project_id = p.project_id AND pm.user_id = $1
|
||||
LEFT JOIN team_member AS tm ON tm.team_id = p.team_id AND tm.user_id = $1
|
||||
WHERE p.project_id = $2;
|
||||
|
@ -5,7 +5,7 @@ SELECT * FROM team;
|
||||
SELECT * FROM team WHERE team_id = $1;
|
||||
|
||||
-- name: CreateTeam :one
|
||||
INSERT INTO team (organization_id, created_at, name, owner) VALUES ($1, $2, $3, $4) RETURNING *;
|
||||
INSERT INTO team (organization_id, created_at, name) VALUES ($1, $2, $3) RETURNING *;
|
||||
|
||||
-- name: DeleteTeamByID :exec
|
||||
DELETE FROM team WHERE team_id = $1;
|
||||
@ -13,14 +13,15 @@ DELETE FROM team WHERE team_id = $1;
|
||||
-- name: GetTeamsForOrganization :many
|
||||
SELECT * FROM team WHERE organization_id = $1;
|
||||
|
||||
-- name: SetTeamOwner :one
|
||||
UPDATE team SET owner = $2 WHERE team_id = $1 RETURNING *;
|
||||
|
||||
-- name: GetOwnedTeamsForUserID :many
|
||||
SELECT * FROM team WHERE owner = $1;
|
||||
|
||||
-- name: GetMemberTeamIDsForUserID :many
|
||||
SELECT team_id FROM team_member WHERE user_id = $1;
|
||||
|
||||
-- name: UpdateTeamOwnerByOwnerID :many
|
||||
UPDATE team SET owner = $2 WHERE owner = $1 RETURNING team_id;
|
||||
-- name: GetTeamRoleForUserID :one
|
||||
SELECT team_id, role_code FROM team_member WHERE user_id = $1 AND team_id = $2;
|
||||
|
||||
-- name: GetTeamRolesForUserID :many
|
||||
SELECT team_id, role_code FROM team_member WHERE user_id = $1;
|
||||
|
||||
-- name: GetTeamsForUserIDWhereAdmin :many
|
||||
SELECT team.* FROM team_member INNER JOIN team
|
||||
ON team.team_id = team_member.team_id WHERE (role_code = 'admin' OR role_code = 'member') AND user_id = $1;
|
||||
|
@ -11,30 +11,23 @@ import (
|
||||
)
|
||||
|
||||
const createTeam = `-- name: CreateTeam :one
|
||||
INSERT INTO team (organization_id, created_at, name, owner) VALUES ($1, $2, $3, $4) RETURNING team_id, created_at, name, organization_id, owner
|
||||
INSERT INTO team (organization_id, created_at, name) VALUES ($1, $2, $3) RETURNING team_id, created_at, name, organization_id
|
||||
`
|
||||
|
||||
type CreateTeamParams struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Name string `json:"name"`
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error) {
|
||||
row := q.db.QueryRowContext(ctx, createTeam,
|
||||
arg.OrganizationID,
|
||||
arg.CreatedAt,
|
||||
arg.Name,
|
||||
arg.Owner,
|
||||
)
|
||||
row := q.db.QueryRowContext(ctx, createTeam, arg.OrganizationID, arg.CreatedAt, arg.Name)
|
||||
var i Team
|
||||
err := row.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -49,7 +42,7 @@ func (q *Queries) DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error {
|
||||
}
|
||||
|
||||
const getAllTeams = `-- name: GetAllTeams :many
|
||||
SELECT team_id, created_at, name, organization_id, owner FROM team
|
||||
SELECT team_id, created_at, name, organization_id FROM team
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
|
||||
@ -66,7 +59,6 @@ func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -108,26 +100,62 @@ func (q *Queries) GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUI
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getOwnedTeamsForUserID = `-- name: GetOwnedTeamsForUserID :many
|
||||
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE owner = $1
|
||||
const getTeamByID = `-- name: GetTeamByID :one
|
||||
SELECT team_id, created_at, name, organization_id FROM team WHERE team_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetOwnedTeamsForUserID(ctx context.Context, owner uuid.UUID) ([]Team, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getOwnedTeamsForUserID, owner)
|
||||
func (q *Queries) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) {
|
||||
row := q.db.QueryRowContext(ctx, getTeamByID, teamID)
|
||||
var i Team
|
||||
err := row.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTeamRoleForUserID = `-- name: GetTeamRoleForUserID :one
|
||||
SELECT team_id, role_code FROM team_member WHERE user_id = $1 AND team_id = $2
|
||||
`
|
||||
|
||||
type GetTeamRoleForUserIDParams struct {
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
}
|
||||
|
||||
type GetTeamRoleForUserIDRow struct {
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTeamRoleForUserID(ctx context.Context, arg GetTeamRoleForUserIDParams) (GetTeamRoleForUserIDRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, getTeamRoleForUserID, arg.UserID, arg.TeamID)
|
||||
var i GetTeamRoleForUserIDRow
|
||||
err := row.Scan(&i.TeamID, &i.RoleCode)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTeamRolesForUserID = `-- name: GetTeamRolesForUserID :many
|
||||
SELECT team_id, role_code FROM team_member WHERE user_id = $1
|
||||
`
|
||||
|
||||
type GetTeamRolesForUserIDRow struct {
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTeamRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetTeamRolesForUserIDRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getTeamRolesForUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Team
|
||||
var items []GetTeamRolesForUserIDRow
|
||||
for rows.Next() {
|
||||
var i Team
|
||||
if err := rows.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
var i GetTeamRolesForUserIDRow
|
||||
if err := rows.Scan(&i.TeamID, &i.RoleCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
@ -141,25 +169,8 @@ func (q *Queries) GetOwnedTeamsForUserID(ctx context.Context, owner uuid.UUID) (
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getTeamByID = `-- name: GetTeamByID :one
|
||||
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE team_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) {
|
||||
row := q.db.QueryRowContext(ctx, getTeamByID, teamID)
|
||||
var i Team
|
||||
err := row.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTeamsForOrganization = `-- name: GetTeamsForOrganization :many
|
||||
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE organization_id = $1
|
||||
SELECT team_id, created_at, name, organization_id FROM team WHERE organization_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error) {
|
||||
@ -176,7 +187,6 @@ func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uu
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -191,50 +201,29 @@ func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uu
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const setTeamOwner = `-- name: SetTeamOwner :one
|
||||
UPDATE team SET owner = $2 WHERE team_id = $1 RETURNING team_id, created_at, name, organization_id, owner
|
||||
const getTeamsForUserIDWhereAdmin = `-- name: GetTeamsForUserIDWhereAdmin :many
|
||||
SELECT team.team_id, team.created_at, team.name, team.organization_id FROM team_member INNER JOIN team
|
||||
ON team.team_id = team_member.team_id WHERE (role_code = 'admin' OR role_code = 'member') AND user_id = $1
|
||||
`
|
||||
|
||||
type SetTeamOwnerParams struct {
|
||||
TeamID uuid.UUID `json:"team_id"`
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
}
|
||||
|
||||
func (q *Queries) SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error) {
|
||||
row := q.db.QueryRowContext(ctx, setTeamOwner, arg.TeamID, arg.Owner)
|
||||
var i Team
|
||||
err := row.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
&i.Owner,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateTeamOwnerByOwnerID = `-- name: UpdateTeamOwnerByOwnerID :many
|
||||
UPDATE team SET owner = $2 WHERE owner = $1 RETURNING team_id
|
||||
`
|
||||
|
||||
type UpdateTeamOwnerByOwnerIDParams struct {
|
||||
Owner uuid.UUID `json:"owner"`
|
||||
Owner_2 uuid.UUID `json:"owner_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateTeamOwnerByOwnerID(ctx context.Context, arg UpdateTeamOwnerByOwnerIDParams) ([]uuid.UUID, error) {
|
||||
rows, err := q.db.QueryContext(ctx, updateTeamOwnerByOwnerID, arg.Owner, arg.Owner_2)
|
||||
func (q *Queries) GetTeamsForUserIDWhereAdmin(ctx context.Context, userID uuid.UUID) ([]Team, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getTeamsForUserIDWhereAdmin, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []uuid.UUID
|
||||
var items []Team
|
||||
for rows.Next() {
|
||||
var team_id uuid.UUID
|
||||
if err := rows.Scan(&team_id); err != nil {
|
||||
var i Team
|
||||
if err := rows.Scan(
|
||||
&i.TeamID,
|
||||
&i.CreatedAt,
|
||||
&i.Name,
|
||||
&i.OrganizationID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, team_id)
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,13 @@ package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||
"github.com/99designs/gqlgen/graphql/handler/lru"
|
||||
@ -15,16 +18,71 @@ import (
|
||||
"github.com/jordanknott/taskcafe/internal/auth"
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
// NewHandler returns a new graphql endpoint handler.
|
||||
func NewHandler(config config.AppConfig, repo db.Repository) http.Handler {
|
||||
srv := handler.New(NewExecutableSchema(Config{
|
||||
c := Config{
|
||||
Resolvers: &Resolver{
|
||||
Config: config,
|
||||
Repository: repo,
|
||||
},
|
||||
}))
|
||||
}
|
||||
c.Directives.HasRole = func(ctx context.Context, obj interface{}, next graphql.Resolver, roles []RoleLevel, level ActionLevel, typeArg ObjectType) (interface{}, error) {
|
||||
role, ok := GetUserRole(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user ID is missing")
|
||||
}
|
||||
if role == "admin" {
|
||||
return next(ctx)
|
||||
} else if level == ActionLevelOrg {
|
||||
return nil, errors.New("must be an org admin")
|
||||
}
|
||||
|
||||
var subjectID uuid.UUID
|
||||
in := graphql.GetResolverContext(ctx).Args["input"]
|
||||
if typeArg == ObjectTypeProject || typeArg == ObjectTypeTeam {
|
||||
val := reflect.ValueOf(in) // could be any underlying type
|
||||
fieldName := "ProjectID"
|
||||
if typeArg == ObjectTypeTeam {
|
||||
fieldName = "TeamID"
|
||||
}
|
||||
subjectID, ok = val.FieldByName(fieldName).Interface().(uuid.UUID)
|
||||
if !ok {
|
||||
return nil, errors.New("error while casting subject uuid")
|
||||
}
|
||||
}
|
||||
|
||||
if level == ActionLevelProject {
|
||||
roles, err := GetProjectRoles(ctx, repo, subjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if roles.TeamRole == "admin" || roles.ProjectRole == "admin" {
|
||||
log.WithFields(log.Fields{"teamRole": roles.TeamRole, "projectRole": roles.ProjectRole}).Info("is team or project role")
|
||||
return next(ctx)
|
||||
}
|
||||
return nil, errors.New("must be a team or project admin")
|
||||
} else if level == ActionLevelTeam {
|
||||
userID, ok := GetUserID(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("user id is missing")
|
||||
}
|
||||
role, err := repo.GetTeamRoleForUserID(ctx, db.GetTeamRoleForUserIDParams{UserID: userID, TeamID: subjectID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role.RoleCode == "admin" {
|
||||
return next(ctx)
|
||||
}
|
||||
return nil, errors.New("must be a team admin")
|
||||
|
||||
}
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
srv := handler.New(NewExecutableSchema(c))
|
||||
srv.AddTransport(transport.Websocket{
|
||||
KeepAlivePingInterval: 10 * time.Second,
|
||||
})
|
||||
@ -55,8 +113,80 @@ func GetUserID(ctx context.Context) (uuid.UUID, bool) {
|
||||
userID, ok := ctx.Value("userID").(uuid.UUID)
|
||||
return userID, ok
|
||||
}
|
||||
func GetUserRole(ctx context.Context) (auth.Role, bool) {
|
||||
role, ok := ctx.Value("org_role").(auth.Role)
|
||||
return role, ok
|
||||
}
|
||||
|
||||
func GetUser(ctx context.Context) (uuid.UUID, auth.Role, bool) {
|
||||
userID, userOK := GetUserID(ctx)
|
||||
role, roleOK := GetUserRole(ctx)
|
||||
return userID, role, userOK && roleOK
|
||||
}
|
||||
|
||||
func GetRestrictedMode(ctx context.Context) (auth.RestrictedMode, bool) {
|
||||
restricted, ok := ctx.Value("restricted_mode").(auth.RestrictedMode)
|
||||
return restricted, ok
|
||||
}
|
||||
|
||||
func GetProjectRoles(ctx context.Context, r db.Repository, projectID uuid.UUID) (db.GetUserRolesForProjectRow, error) {
|
||||
userID, ok := GetUserID(ctx)
|
||||
if !ok {
|
||||
return db.GetUserRolesForProjectRow{}, errors.New("user ID is not found")
|
||||
}
|
||||
return r.GetUserRolesForProject(ctx, db.GetUserRolesForProjectParams{UserID: userID, ProjectID: projectID})
|
||||
}
|
||||
|
||||
func ConvertToRoleCode(r string) RoleCode {
|
||||
if r == RoleCodeAdmin.String() {
|
||||
return RoleCodeAdmin
|
||||
}
|
||||
if r == RoleCodeMember.String() {
|
||||
return RoleCodeMember
|
||||
}
|
||||
return RoleCodeObserver
|
||||
}
|
||||
|
||||
func RequireTeamAdmin(ctx context.Context, r db.Repository, teamID uuid.UUID) error {
|
||||
userID, role, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
return errors.New("internal: user id is not set")
|
||||
}
|
||||
teamRole, err := r.GetTeamRoleForUserID(ctx, db.GetTeamRoleForUserIDParams{UserID: userID, TeamID: teamID})
|
||||
isAdmin := role == auth.RoleAdmin
|
||||
isTeamAdmin := err == nil && ConvertToRoleCode(teamRole.RoleCode) == RoleCodeAdmin
|
||||
if !(isAdmin || isTeamAdmin) {
|
||||
return &gqlerror.Error{
|
||||
Message: "organization or team admin role required",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "2-400",
|
||||
},
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RequireProjectOrTeamAdmin(ctx context.Context, r db.Repository, projectID uuid.UUID) error {
|
||||
role, ok := GetUserRole(ctx)
|
||||
if !ok {
|
||||
return errors.New("user ID is not set")
|
||||
}
|
||||
if role == auth.RoleAdmin {
|
||||
return nil
|
||||
}
|
||||
roles, err := GetProjectRoles(ctx, r, projectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !(roles.ProjectRole == "admin" || roles.TeamRole == "admin") {
|
||||
return &gqlerror.Error{
|
||||
Message: "You must be a team or project admin",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "4-400",
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -8,15 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func GetOwnedList(ctx context.Context, r db.Repository, user db.UserAccount) (*OwnedList, error) {
|
||||
ownedTeams, err := r.GetOwnedTeamsForUserID(ctx, user.UserID)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &OwnedList{}, err
|
||||
}
|
||||
ownedProjects, err := r.GetOwnedProjectsForUserID(ctx, user.UserID)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &OwnedList{}, err
|
||||
}
|
||||
return &OwnedList{Teams: ownedTeams, Projects: ownedProjects}, nil
|
||||
return &OwnedList{}, nil
|
||||
}
|
||||
func GetMemberList(ctx context.Context, r db.Repository, user db.UserAccount) (*MemberList, error) {
|
||||
projectMemberIDs, err := r.GetMemberProjectIDsForUserID(ctx, user.UserID)
|
||||
|
@ -152,7 +152,7 @@ type DeleteUserAccountPayload struct {
|
||||
}
|
||||
|
||||
type FindProject struct {
|
||||
ProjectID string `json:"projectId"`
|
||||
ProjectID uuid.UUID `json:"projectID"`
|
||||
}
|
||||
|
||||
type FindTask struct {
|
||||
@ -171,6 +171,12 @@ type LogoutUser struct {
|
||||
UserID string `json:"userID"`
|
||||
}
|
||||
|
||||
type MePayload struct {
|
||||
User *db.UserAccount `json:"user"`
|
||||
TeamRoles []TeamRole `json:"teamRoles"`
|
||||
ProjectRoles []ProjectRole `json:"projectRoles"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Role *db.Role `json:"role"`
|
||||
@ -255,6 +261,11 @@ type ProfileIcon struct {
|
||||
BgColor *string `json:"bgColor"`
|
||||
}
|
||||
|
||||
type ProjectRole struct {
|
||||
ProjectID uuid.UUID `json:"projectID"`
|
||||
RoleCode RoleCode `json:"roleCode"`
|
||||
}
|
||||
|
||||
type ProjectsFilter struct {
|
||||
TeamID *uuid.UUID `json:"teamID"`
|
||||
}
|
||||
@ -263,17 +274,6 @@ type RemoveTaskLabelInput struct {
|
||||
TaskLabelID uuid.UUID `json:"taskLabelID"`
|
||||
}
|
||||
|
||||
type SetProjectOwner struct {
|
||||
ProjectID uuid.UUID `json:"projectID"`
|
||||
OwnerID uuid.UUID `json:"ownerID"`
|
||||
}
|
||||
|
||||
type SetProjectOwnerPayload struct {
|
||||
Ok bool `json:"ok"`
|
||||
PrevOwner *Member `json:"prevOwner"`
|
||||
NewOwner *Member `json:"newOwner"`
|
||||
}
|
||||
|
||||
type SetTaskChecklistItemComplete struct {
|
||||
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
|
||||
Complete bool `json:"complete"`
|
||||
@ -284,21 +284,15 @@ type SetTaskComplete struct {
|
||||
Complete bool `json:"complete"`
|
||||
}
|
||||
|
||||
type SetTeamOwner struct {
|
||||
TeamID uuid.UUID `json:"teamID"`
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
}
|
||||
|
||||
type SetTeamOwnerPayload struct {
|
||||
Ok bool `json:"ok"`
|
||||
PrevOwner *Member `json:"prevOwner"`
|
||||
NewOwner *Member `json:"newOwner"`
|
||||
}
|
||||
|
||||
type TaskBadges struct {
|
||||
Checklist *ChecklistBadge `json:"checklist"`
|
||||
}
|
||||
|
||||
type TeamRole struct {
|
||||
TeamID uuid.UUID `json:"teamID"`
|
||||
RoleCode RoleCode `json:"roleCode"`
|
||||
}
|
||||
|
||||
type ToggleTaskLabelInput struct {
|
||||
TaskID uuid.UUID `json:"taskID"`
|
||||
ProjectLabelID uuid.UUID `json:"projectLabelID"`
|
||||
@ -409,8 +403,9 @@ type UpdateTeamMemberRole struct {
|
||||
}
|
||||
|
||||
type UpdateTeamMemberRolePayload struct {
|
||||
Ok bool `json:"ok"`
|
||||
Member *Member `json:"member"`
|
||||
Ok bool `json:"ok"`
|
||||
TeamID uuid.UUID `json:"teamID"`
|
||||
Member *Member `json:"member"`
|
||||
}
|
||||
|
||||
type UpdateUserPassword struct {
|
||||
@ -432,6 +427,94 @@ type UpdateUserRolePayload struct {
|
||||
User *db.UserAccount `json:"user"`
|
||||
}
|
||||
|
||||
type ActionLevel string
|
||||
|
||||
const (
|
||||
ActionLevelOrg ActionLevel = "ORG"
|
||||
ActionLevelTeam ActionLevel = "TEAM"
|
||||
ActionLevelProject ActionLevel = "PROJECT"
|
||||
)
|
||||
|
||||
var AllActionLevel = []ActionLevel{
|
||||
ActionLevelOrg,
|
||||
ActionLevelTeam,
|
||||
ActionLevelProject,
|
||||
}
|
||||
|
||||
func (e ActionLevel) IsValid() bool {
|
||||
switch e {
|
||||
case ActionLevelOrg, ActionLevelTeam, ActionLevelProject:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ActionLevel) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *ActionLevel) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ActionLevel(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid ActionLevel", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ActionLevel) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type ObjectType string
|
||||
|
||||
const (
|
||||
ObjectTypeOrg ObjectType = "ORG"
|
||||
ObjectTypeTeam ObjectType = "TEAM"
|
||||
ObjectTypeProject ObjectType = "PROJECT"
|
||||
ObjectTypeTask ObjectType = "TASK"
|
||||
)
|
||||
|
||||
var AllObjectType = []ObjectType{
|
||||
ObjectTypeOrg,
|
||||
ObjectTypeTeam,
|
||||
ObjectTypeProject,
|
||||
ObjectTypeTask,
|
||||
}
|
||||
|
||||
func (e ObjectType) IsValid() bool {
|
||||
switch e {
|
||||
case ObjectTypeOrg, ObjectTypeTeam, ObjectTypeProject, ObjectTypeTask:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ObjectType) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *ObjectType) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ObjectType(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid ObjectType", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ObjectType) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type RoleCode string
|
||||
|
||||
const (
|
||||
@ -476,3 +559,44 @@ func (e *RoleCode) UnmarshalGQL(v interface{}) error {
|
||||
func (e RoleCode) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type RoleLevel string
|
||||
|
||||
const (
|
||||
RoleLevelAdmin RoleLevel = "ADMIN"
|
||||
RoleLevelMember RoleLevel = "MEMBER"
|
||||
)
|
||||
|
||||
var AllRoleLevel = []RoleLevel{
|
||||
RoleLevelAdmin,
|
||||
RoleLevelMember,
|
||||
}
|
||||
|
||||
func (e RoleLevel) IsValid() bool {
|
||||
switch e {
|
||||
case RoleLevelAdmin, RoleLevelMember:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e RoleLevel) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *RoleLevel) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = RoleLevel(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid RoleLevel", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e RoleLevel) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
@ -97,7 +97,6 @@ type Project {
|
||||
createdAt: Time!
|
||||
name: String!
|
||||
team: Team!
|
||||
owner: Member!
|
||||
taskGroups: [TaskGroup!]!
|
||||
members: [Member!]!
|
||||
labels: [ProjectLabel!]!
|
||||
@ -157,6 +156,26 @@ type TaskChecklist {
|
||||
items: [TaskChecklistItem!]!
|
||||
}
|
||||
|
||||
enum RoleLevel {
|
||||
ADMIN
|
||||
MEMBER
|
||||
}
|
||||
|
||||
enum ActionLevel {
|
||||
ORG
|
||||
TEAM
|
||||
PROJECT
|
||||
}
|
||||
|
||||
enum ObjectType {
|
||||
ORG
|
||||
TEAM
|
||||
PROJECT
|
||||
TASK
|
||||
}
|
||||
|
||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
organizations: [Organization!]!
|
||||
users: [UserAccount!]!
|
||||
@ -168,11 +187,27 @@ type Query {
|
||||
teams: [Team!]!
|
||||
labelColors: [LabelColor!]!
|
||||
taskGroups: [TaskGroup!]!
|
||||
me: UserAccount!
|
||||
me: MePayload!
|
||||
}
|
||||
|
||||
type Mutation
|
||||
|
||||
type TeamRole {
|
||||
teamID: UUID!
|
||||
roleCode: RoleCode!
|
||||
}
|
||||
|
||||
type ProjectRole {
|
||||
projectID: UUID!
|
||||
roleCode: RoleCode!
|
||||
}
|
||||
|
||||
type MePayload {
|
||||
user: UserAccount!
|
||||
teamRoles: [TeamRole!]!
|
||||
projectRoles: [ProjectRole!]!
|
||||
}
|
||||
|
||||
input ProjectsFilter {
|
||||
teamID: UUID
|
||||
}
|
||||
@ -182,7 +217,7 @@ input FindUser {
|
||||
}
|
||||
|
||||
input FindProject {
|
||||
projectId: String!
|
||||
projectID: UUID!
|
||||
}
|
||||
|
||||
input FindTask {
|
||||
@ -194,9 +229,11 @@ input FindTeam {
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createProject(input: NewProject!): Project!
|
||||
deleteProject(input: DeleteProject!): DeleteProjectPayload!
|
||||
updateProjectName(input: UpdateProjectName): Project!
|
||||
createProject(input: NewProject!): Project! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
deleteProject(input: DeleteProject!):
|
||||
DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectName(input: UpdateProjectName):
|
||||
Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewProject {
|
||||
@ -221,11 +258,16 @@ type DeleteProjectPayload {
|
||||
|
||||
|
||||
extend type Mutation {
|
||||
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
|
||||
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
|
||||
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
|
||||
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
|
||||
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
|
||||
createProjectLabel(input: NewProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteProjectLabel(input: DeleteProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabel(input: UpdateProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabelName(input: UpdateProjectLabelName!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabelColor(input: UpdateProjectLabelColor!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewProjectLabel {
|
||||
@ -255,10 +297,12 @@ input UpdateProjectLabelColor {
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
|
||||
deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
|
||||
updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
|
||||
setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
|
||||
createProjectMember(input: CreateProjectMember!):
|
||||
CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteProjectMember(input: DeleteProjectMember!):
|
||||
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectMemberRole(input: UpdateProjectMemberRole!):
|
||||
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input CreateProjectMember {
|
||||
@ -293,16 +337,6 @@ type UpdateProjectMemberRolePayload {
|
||||
member: Member!
|
||||
}
|
||||
|
||||
input SetProjectOwner {
|
||||
projectID: UUID!
|
||||
ownerID: UUID!
|
||||
}
|
||||
type SetProjectOwnerPayload {
|
||||
ok: Boolean!
|
||||
prevOwner: Member!
|
||||
newOwner: Member!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createTask(input: NewTask!): Task!
|
||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||
@ -506,8 +540,10 @@ extend type Mutation {
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
|
||||
createTeam(input: NewTeam!): Team!
|
||||
deleteTeam(input: DeleteTeam!):
|
||||
DeleteTeamPayload! @hasRole(roles:[ ADMIN], level: TEAM, type: TEAM)
|
||||
createTeam(input: NewTeam!):
|
||||
Team! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
}
|
||||
|
||||
input NewTeam {
|
||||
@ -526,10 +562,11 @@ type DeleteTeamPayload {
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
|
||||
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
|
||||
updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
|
||||
deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload!
|
||||
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
updateTeamMemberRole(input: UpdateTeamMemberRole!):
|
||||
UpdateTeamMemberRolePayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
|
||||
}
|
||||
|
||||
input DeleteTeamMember {
|
||||
@ -562,29 +599,23 @@ input UpdateTeamMemberRole {
|
||||
|
||||
type UpdateTeamMemberRolePayload {
|
||||
ok: Boolean!
|
||||
member: Member!
|
||||
}
|
||||
|
||||
input SetTeamOwner {
|
||||
teamID: UUID!
|
||||
userID: UUID!
|
||||
}
|
||||
|
||||
type SetTeamOwnerPayload {
|
||||
ok: Boolean!
|
||||
prevOwner: Member!
|
||||
newOwner: Member!
|
||||
member: Member!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||
createUserAccount(input: NewUserAccount!): UserAccount!
|
||||
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
|
||||
createUserAccount(input: NewUserAccount!):
|
||||
UserAccount! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
deleteUserAccount(input: DeleteUserAccount!):
|
||||
DeleteUserAccountPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
clearProfileAvatar: UserAccount!
|
||||
|
||||
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
|
||||
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
|
||||
updateUserRole(input: UpdateUserRole!):
|
||||
UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
}
|
||||
|
||||
input UpdateUserPassword {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jordanknott/taskcafe/internal/auth"
|
||||
"github.com/jordanknott/taskcafe/internal/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
@ -23,7 +24,8 @@ func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.U
|
||||
|
||||
func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*db.Project, error) {
|
||||
createdAt := time.Now().UTC()
|
||||
project, err := r.Repository.CreateProject(ctx, db.CreateProjectParams{input.UserID, input.TeamID, createdAt, input.Name})
|
||||
log.WithFields(log.Fields{"userID": input.UserID, "name": input.Name, "teamID": input.TeamID}).Info("creating new project")
|
||||
project, err := r.Repository.CreateProject(ctx, db.CreateProjectParams{input.TeamID, createdAt, input.Name})
|
||||
return &project, err
|
||||
}
|
||||
|
||||
@ -146,9 +148,6 @@ func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input Delete
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) {
|
||||
if input.RoleCode == RoleCodeOwner {
|
||||
return &UpdateProjectMemberRolePayload{Ok: false}, errors.New("can not set project owner through this mutation")
|
||||
}
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account")
|
||||
@ -179,64 +178,6 @@ func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input Up
|
||||
return &UpdateProjectMemberRolePayload{Ok: true, Member: &member}, err
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SetProjectOwner(ctx context.Context, input SetProjectOwner) (*SetProjectOwnerPayload, error) {
|
||||
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
|
||||
if project.Owner == input.OwnerID {
|
||||
return &SetProjectOwnerPayload{Ok: false}, errors.New("new project owner is already project owner")
|
||||
}
|
||||
_, err = r.Repository.SetProjectOwner(ctx, db.SetProjectOwnerParams{Owner: input.OwnerID, ProjectID: input.ProjectID})
|
||||
if err != nil {
|
||||
return &SetProjectOwnerPayload{Ok: false}, err
|
||||
}
|
||||
err = r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{ProjectID: input.ProjectID, UserID: input.OwnerID})
|
||||
if err != nil {
|
||||
return &SetProjectOwnerPayload{Ok: false}, err
|
||||
}
|
||||
|
||||
addedAt := time.Now().UTC()
|
||||
_, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID,
|
||||
UserID: project.Owner, RoleCode: RoleCodeAdmin.String(), AddedAt: addedAt})
|
||||
if err != nil {
|
||||
return &SetProjectOwnerPayload{Ok: false}, err
|
||||
}
|
||||
|
||||
oldUser, err := r.Repository.GetUserAccountByID(ctx, project.Owner)
|
||||
var url *string
|
||||
if oldUser.ProfileAvatarUrl.Valid {
|
||||
url = &oldUser.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon := &ProfileIcon{url, &oldUser.Initials, &oldUser.ProfileBgColor}
|
||||
oldUserRole := db.Role{Code: "admin", Name: "Admin"}
|
||||
oldMember := &Member{
|
||||
ID: oldUser.UserID,
|
||||
Username: oldUser.Username,
|
||||
FullName: oldUser.FullName,
|
||||
ProfileIcon: profileIcon,
|
||||
Role: &oldUserRole,
|
||||
}
|
||||
|
||||
newUser, err := r.Repository.GetUserAccountByID(ctx, input.OwnerID)
|
||||
|
||||
if newUser.ProfileAvatarUrl.Valid {
|
||||
url = &newUser.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon = &ProfileIcon{url, &newUser.Initials, &newUser.ProfileBgColor}
|
||||
newUserRole := db.Role{Code: "owner", Name: "Owner"}
|
||||
newMember := &Member{
|
||||
ID: newUser.UserID,
|
||||
Username: newUser.Username,
|
||||
FullName: newUser.FullName,
|
||||
ProfileIcon: profileIcon,
|
||||
Role: &newUserRole,
|
||||
}
|
||||
|
||||
return &SetProjectOwnerPayload{
|
||||
Ok: true,
|
||||
PrevOwner: oldMember,
|
||||
NewOwner: newMember,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.Task, error) {
|
||||
taskGroupID, err := uuid.Parse(input.TaskGroupID)
|
||||
createdAt := time.Now().UTC()
|
||||
@ -574,71 +515,21 @@ func (r *mutationResolver) DeleteTeam(ctx context.Context, input DeleteTeam) (*D
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateTeam(ctx context.Context, input NewTeam) (*db.Team, error) {
|
||||
userID, ok := GetUserID(ctx)
|
||||
_, role, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
return &db.Team{}, fmt.Errorf("internal server error")
|
||||
return &db.Team{}, nil
|
||||
}
|
||||
createdAt := time.Now().UTC()
|
||||
team, err := r.Repository.CreateTeam(ctx, db.CreateTeamParams{OrganizationID: input.OrganizationID, CreatedAt: createdAt, Name: input.Name, Owner: userID})
|
||||
return &team, err
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SetTeamOwner(ctx context.Context, input SetTeamOwner) (*SetTeamOwnerPayload, error) {
|
||||
team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
|
||||
if team.Owner == input.UserID {
|
||||
return &SetTeamOwnerPayload{Ok: false}, errors.New("new project owner is already project owner")
|
||||
if role == auth.RoleAdmin {
|
||||
createdAt := time.Now().UTC()
|
||||
team, err := r.Repository.CreateTeam(ctx, db.CreateTeamParams{OrganizationID: input.OrganizationID, CreatedAt: createdAt, Name: input.Name})
|
||||
return &team, err
|
||||
}
|
||||
_, err = r.Repository.SetTeamOwner(ctx, db.SetTeamOwnerParams{Owner: input.UserID, TeamID: input.TeamID})
|
||||
if err != nil {
|
||||
return &SetTeamOwnerPayload{Ok: false}, errors.New("new project owner is already project owner")
|
||||
return &db.Team{}, &gqlerror.Error{
|
||||
Message: "You must be an organization admin to create new teams",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "1-400",
|
||||
},
|
||||
}
|
||||
err = r.Repository.DeleteTeamMember(ctx, db.DeleteTeamMemberParams{TeamID: input.TeamID, UserID: input.UserID})
|
||||
if err != nil {
|
||||
return &SetTeamOwnerPayload{Ok: false}, errors.New("new project owner is already project owner")
|
||||
}
|
||||
|
||||
addedAt := time.Now().UTC()
|
||||
_, err = r.Repository.CreateTeamMember(ctx, db.CreateTeamMemberParams{TeamID: input.TeamID,
|
||||
UserID: team.Owner, RoleCode: RoleCodeAdmin.String(), Addeddate: addedAt})
|
||||
if err != nil {
|
||||
return &SetTeamOwnerPayload{Ok: false}, errors.New("new project owner is already project owner")
|
||||
}
|
||||
|
||||
oldUser, err := r.Repository.GetUserAccountByID(ctx, team.Owner)
|
||||
var url *string
|
||||
if oldUser.ProfileAvatarUrl.Valid {
|
||||
url = &oldUser.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon := &ProfileIcon{url, &oldUser.Initials, &oldUser.ProfileBgColor}
|
||||
oldUserRole := db.Role{Code: "admin", Name: "Admin"}
|
||||
oldMember := &Member{
|
||||
ID: oldUser.UserID,
|
||||
Username: oldUser.Username,
|
||||
FullName: oldUser.FullName,
|
||||
ProfileIcon: profileIcon,
|
||||
Role: &oldUserRole,
|
||||
}
|
||||
|
||||
newUser, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
||||
|
||||
if newUser.ProfileAvatarUrl.Valid {
|
||||
url = &newUser.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon = &ProfileIcon{url, &newUser.Initials, &newUser.ProfileBgColor}
|
||||
newUserRole := db.Role{Code: "owner", Name: "Owner"}
|
||||
newMember := &Member{
|
||||
ID: newUser.UserID,
|
||||
Username: newUser.Username,
|
||||
FullName: newUser.FullName,
|
||||
ProfileIcon: profileIcon,
|
||||
Role: &newUserRole,
|
||||
}
|
||||
|
||||
return &SetTeamOwnerPayload{
|
||||
Ok: true,
|
||||
PrevOwner: oldMember,
|
||||
NewOwner: newMember,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateTeamMember(ctx context.Context, input CreateTeamMember) (*CreateTeamMemberPayload, error) {
|
||||
@ -669,9 +560,6 @@ func (r *mutationResolver) CreateTeamMember(ctx context.Context, input CreateTea
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateTeamMemberRole(ctx context.Context, input UpdateTeamMemberRole) (*UpdateTeamMemberRolePayload, error) {
|
||||
if input.RoleCode == RoleCodeOwner || input.RoleCode == RoleCodeObserver {
|
||||
return &UpdateTeamMemberRolePayload{Ok: false}, errors.New("can not set project owner through this mutation")
|
||||
}
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account")
|
||||
@ -699,29 +587,12 @@ func (r *mutationResolver) UpdateTeamMemberRole(ctx context.Context, input Updat
|
||||
member := Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
|
||||
Role: &db.Role{Code: role.Code, Name: role.Name},
|
||||
}
|
||||
return &UpdateTeamMemberRolePayload{Ok: true, Member: &member}, err
|
||||
return &UpdateTeamMemberRolePayload{Ok: true, Member: &member, TeamID: input.TeamID}, err
|
||||
}
|
||||
|
||||
func (r *mutationResolver) DeleteTeamMember(ctx context.Context, input DeleteTeamMember) (*DeleteTeamMemberPayload, error) {
|
||||
ownedProjects, err := r.Repository.GetOwnedTeamProjectsForUserID(ctx, db.GetOwnedTeamProjectsForUserIDParams{TeamID: input.TeamID, Owner: input.UserID})
|
||||
if err != nil {
|
||||
return &DeleteTeamMemberPayload{}, err
|
||||
}
|
||||
|
||||
_, err = r.Repository.GetTeamMemberByID(ctx, db.GetTeamMemberByIDParams{TeamID: input.TeamID, UserID: input.UserID})
|
||||
if err != nil {
|
||||
return &DeleteTeamMemberPayload{}, err
|
||||
}
|
||||
err = r.Repository.DeleteTeamMember(ctx, db.DeleteTeamMemberParams{TeamID: input.TeamID, UserID: input.UserID})
|
||||
if err != nil {
|
||||
return &DeleteTeamMemberPayload{}, err
|
||||
}
|
||||
if input.NewOwnerID != nil {
|
||||
for _, projectID := range ownedProjects {
|
||||
_, err = r.Repository.SetProjectOwner(ctx, db.SetProjectOwnerParams{ProjectID: projectID, Owner: *input.NewOwnerID})
|
||||
}
|
||||
}
|
||||
return &DeleteTeamMemberPayload{TeamID: input.TeamID, UserID: input.UserID}, nil
|
||||
err := r.Repository.DeleteTeamMember(ctx, db.DeleteTeamMemberParams{TeamID: input.TeamID, UserID: input.UserID})
|
||||
return &DeleteTeamMemberPayload{TeamID: input.TeamID, UserID: input.UserID}, err
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateRefreshToken(ctx context.Context, input NewRefreshToken) (*db.RefreshToken, error) {
|
||||
@ -733,6 +604,18 @@ func (r *mutationResolver) CreateRefreshToken(ctx context.Context, input NewRefr
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateUserAccount(ctx context.Context, input NewUserAccount) (*db.UserAccount, error) {
|
||||
_, role, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
return &db.UserAccount{}, nil
|
||||
}
|
||||
if role != auth.RoleAdmin {
|
||||
return &db.UserAccount{}, &gqlerror.Error{
|
||||
Message: "Must be an organization admin",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "0-400",
|
||||
},
|
||||
}
|
||||
}
|
||||
createdAt := time.Now().UTC()
|
||||
hashedPwd, err := bcrypt.GenerateFromPassword([]byte(input.Password), 14)
|
||||
if err != nil {
|
||||
@ -751,35 +634,25 @@ func (r *mutationResolver) CreateUserAccount(ctx context.Context, input NewUserA
|
||||
}
|
||||
|
||||
func (r *mutationResolver) DeleteUserAccount(ctx context.Context, input DeleteUserAccount) (*DeleteUserAccountPayload, error) {
|
||||
_, role, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
return &DeleteUserAccountPayload{Ok: false}, nil
|
||||
}
|
||||
if role != auth.RoleAdmin {
|
||||
return &DeleteUserAccountPayload{Ok: false}, &gqlerror.Error{
|
||||
Message: "User not found",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "0-401",
|
||||
},
|
||||
}
|
||||
}
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
||||
if err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
|
||||
var newOwnerID uuid.UUID
|
||||
if input.NewOwnerID == nil {
|
||||
sysUser, err := r.Repository.GetUserAccountByUsername(ctx, "system")
|
||||
if err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
newOwnerID = sysUser.UserID
|
||||
} else {
|
||||
newOwnerID = *input.NewOwnerID
|
||||
}
|
||||
projectIDs, err := r.Repository.UpdateProjectOwnerByOwnerID(ctx, db.UpdateProjectOwnerByOwnerIDParams{Owner: user.UserID, Owner_2: newOwnerID})
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
for _, projectID := range projectIDs {
|
||||
r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{UserID: newOwnerID, ProjectID: projectID})
|
||||
}
|
||||
teamIDs, err := r.Repository.UpdateTeamOwnerByOwnerID(ctx, db.UpdateTeamOwnerByOwnerIDParams{Owner: user.UserID, Owner_2: newOwnerID})
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
}
|
||||
for _, teamID := range teamIDs {
|
||||
r.Repository.DeleteTeamMember(ctx, db.DeleteTeamMemberParams{UserID: newOwnerID, TeamID: teamID})
|
||||
}
|
||||
// TODO(jordanknott) migrate admin ownership
|
||||
|
||||
err = r.Repository.DeleteUserAccountByID(ctx, input.UserID)
|
||||
if err != nil {
|
||||
return &DeleteUserAccountPayload{Ok: false}, err
|
||||
@ -822,6 +695,18 @@ func (r *mutationResolver) UpdateUserPassword(ctx context.Context, input UpdateU
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error) {
|
||||
_, role, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
return &UpdateUserRolePayload{}, nil
|
||||
}
|
||||
if role != auth.RoleAdmin {
|
||||
return &UpdateUserRolePayload{}, &gqlerror.Error{
|
||||
Message: "User not found",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "0-401",
|
||||
},
|
||||
}
|
||||
}
|
||||
user, err := r.Repository.UpdateUserRole(ctx, db.UpdateUserRoleParams{RoleCode: input.RoleCode.String(), UserID: input.UserID})
|
||||
if err != nil {
|
||||
return &UpdateUserRolePayload{}, err
|
||||
@ -839,26 +724,11 @@ func (r *projectResolver) ID(ctx context.Context, obj *db.Project) (uuid.UUID, e
|
||||
|
||||
func (r *projectResolver) Team(ctx context.Context, obj *db.Project) (*db.Team, error) {
|
||||
team, err := r.Repository.GetTeamByID(ctx, obj.TeamID)
|
||||
return &team, err
|
||||
}
|
||||
|
||||
func (r *projectResolver) Owner(ctx context.Context, obj *db.Project) (*Member, error) {
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, obj.Owner)
|
||||
if err != nil {
|
||||
return &Member{}, err
|
||||
log.WithFields(log.Fields{"teamID": obj.TeamID, "projectID": obj.ProjectID}).WithError(err).Error("issue while getting team for project")
|
||||
return &team, err
|
||||
}
|
||||
var url *string
|
||||
if user.ProfileAvatarUrl.Valid {
|
||||
url = &user.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
|
||||
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: obj.ProjectID})
|
||||
if user.ProfileAvatarUrl.Valid {
|
||||
url = &user.ProfileAvatarUrl.String
|
||||
}
|
||||
return &Member{ID: obj.Owner, FullName: user.FullName, ProfileIcon: profileIcon,
|
||||
Role: &db.Role{Code: role.Code, Name: role.Name},
|
||||
}, nil
|
||||
return &team, nil
|
||||
}
|
||||
|
||||
func (r *projectResolver) TaskGroups(ctx context.Context, obj *db.Project) ([]db.TaskGroup, error) {
|
||||
@ -866,24 +736,7 @@ func (r *projectResolver) TaskGroups(ctx context.Context, obj *db.Project) ([]db
|
||||
}
|
||||
|
||||
func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Member, error) {
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, obj.Owner)
|
||||
members := []Member{}
|
||||
if err == sql.ErrNoRows {
|
||||
return members, nil
|
||||
}
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account by ID")
|
||||
return members, err
|
||||
}
|
||||
var url *string
|
||||
if user.ProfileAvatarUrl.Valid {
|
||||
url = &user.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
|
||||
members = append(members, Member{
|
||||
ID: obj.Owner, FullName: user.FullName, ProfileIcon: profileIcon, Username: user.Username,
|
||||
Role: &db.Role{Code: "owner", Name: "Owner"},
|
||||
})
|
||||
projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get project members for project id")
|
||||
@ -891,7 +744,7 @@ func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Membe
|
||||
}
|
||||
|
||||
for _, projectMember := range projectMembers {
|
||||
user, err = r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account by ID")
|
||||
return members, err
|
||||
@ -964,11 +817,12 @@ func (r *queryResolver) FindUser(ctx context.Context, input FindUser) (*db.UserA
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db.Project, error) {
|
||||
projectID, err := uuid.Parse(input.ProjectID)
|
||||
if err != nil {
|
||||
return &db.Project{}, err
|
||||
userID, role, ok := GetUser(ctx)
|
||||
log.WithFields(log.Fields{"userID": userID, "role": role}).Info("find project user")
|
||||
if !ok {
|
||||
return &db.Project{}, nil
|
||||
}
|
||||
project, err := r.Repository.GetProjectByID(ctx, projectID)
|
||||
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
|
||||
if err == sql.ErrNoRows {
|
||||
return &db.Project{}, &gqlerror.Error{
|
||||
Message: "Project not found",
|
||||
@ -977,7 +831,26 @@ func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db
|
||||
},
|
||||
}
|
||||
}
|
||||
return &project, err
|
||||
if role == auth.RoleAdmin {
|
||||
return &project, nil
|
||||
}
|
||||
|
||||
projectRoles, err := GetProjectRoles(ctx, r.Repository, input.ProjectID)
|
||||
log.WithFields(log.Fields{"projectID": input.ProjectID, "teamRole": projectRoles.TeamRole, "projectRole": projectRoles.ProjectRole}).Info("get project roles ")
|
||||
if err != nil {
|
||||
return &project, err
|
||||
}
|
||||
|
||||
if projectRoles.TeamRole == "" && projectRoles.ProjectRole == "" {
|
||||
return &db.Project{}, &gqlerror.Error{
|
||||
Message: "project not accessible",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "11-400",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &project, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) {
|
||||
@ -986,10 +859,57 @@ func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task,
|
||||
}
|
||||
|
||||
func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]db.Project, error) {
|
||||
userID, orgRole, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
log.Info("user id was not found from middleware")
|
||||
return []db.Project{}, nil
|
||||
}
|
||||
log.WithFields(log.Fields{"userID": userID}).Info("fetching projects")
|
||||
|
||||
if input != nil {
|
||||
return r.Repository.GetAllProjectsForTeam(ctx, *input.TeamID)
|
||||
}
|
||||
return r.Repository.GetAllProjects(ctx)
|
||||
|
||||
if orgRole == "admin" {
|
||||
log.Info("showing all projects for admin")
|
||||
return r.Repository.GetAllProjects(ctx)
|
||||
}
|
||||
|
||||
teams, err := r.Repository.GetTeamsForUserIDWhereAdmin(ctx, userID)
|
||||
projects := make(map[string]db.Project)
|
||||
for _, team := range teams {
|
||||
log.WithFields(log.Fields{"teamID": team.TeamID}).Info("found team")
|
||||
teamProjects, err := r.Repository.GetAllProjectsForTeam(ctx, team.TeamID)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
log.Info("issue getting team projects")
|
||||
return []db.Project{}, nil
|
||||
}
|
||||
for _, project := range teamProjects {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding team project")
|
||||
projects[project.ProjectID.String()] = project
|
||||
}
|
||||
}
|
||||
|
||||
visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
|
||||
if err != nil {
|
||||
log.Info("user id was not found from middleware")
|
||||
return []db.Project{}, nil
|
||||
}
|
||||
for _, project := range visibleProjects {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("found visible project")
|
||||
if _, ok := projects[project.ProjectID.String()]; !ok {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding visible project")
|
||||
projects[project.ProjectID.String()] = project
|
||||
}
|
||||
}
|
||||
log.WithFields(log.Fields{"projectLength": len(projects)}).Info("making projects")
|
||||
allProjects := make([]db.Project, 0, len(projects))
|
||||
for _, project := range projects {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("add project to final list")
|
||||
allProjects = append(allProjects, project)
|
||||
}
|
||||
log.Info(allProjects)
|
||||
return allProjects, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindTeam(ctx context.Context, input FindTeam) (*db.Team, error) {
|
||||
@ -1001,7 +921,47 @@ func (r *queryResolver) FindTeam(ctx context.Context, input FindTeam) (*db.Team,
|
||||
}
|
||||
|
||||
func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
|
||||
return r.Repository.GetAllTeams(ctx)
|
||||
userID, orgRole, ok := GetUser(ctx)
|
||||
if !ok {
|
||||
log.Error("userID or orgRole does not exist!")
|
||||
return []db.Team{}, errors.New("internal error")
|
||||
}
|
||||
if orgRole == "admin" {
|
||||
return r.Repository.GetAllTeams(ctx)
|
||||
}
|
||||
|
||||
teams := make(map[string]db.Team)
|
||||
adminTeams, err := r.Repository.GetTeamsForUserIDWhereAdmin(ctx, userID)
|
||||
if err != nil {
|
||||
return []db.Team{}, err
|
||||
}
|
||||
|
||||
for _, team := range adminTeams {
|
||||
teams[team.TeamID.String()] = team
|
||||
}
|
||||
|
||||
visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
|
||||
if err != nil {
|
||||
log.Info("user id was not found from middleware")
|
||||
return []db.Team{}, err
|
||||
}
|
||||
for _, project := range visibleProjects {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("found visible project")
|
||||
if _, ok := teams[project.ProjectID.String()]; !ok {
|
||||
log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding visible project")
|
||||
team, err := r.Repository.GetTeamByID(ctx, project.TeamID)
|
||||
if err != nil {
|
||||
log.Info("user id was not found from middleware")
|
||||
return []db.Team{}, err
|
||||
}
|
||||
teams[project.TeamID.String()] = team
|
||||
}
|
||||
}
|
||||
foundTeams := make([]db.Team, 0, len(teams))
|
||||
for _, team := range teams {
|
||||
foundTeams = append(foundTeams, team)
|
||||
}
|
||||
return foundTeams, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) LabelColors(ctx context.Context) ([]db.LabelColor, error) {
|
||||
@ -1012,19 +972,37 @@ func (r *queryResolver) TaskGroups(ctx context.Context) ([]db.TaskGroup, error)
|
||||
return r.Repository.GetAllTaskGroups(ctx)
|
||||
}
|
||||
|
||||
func (r *queryResolver) Me(ctx context.Context) (*db.UserAccount, error) {
|
||||
func (r *queryResolver) Me(ctx context.Context) (*MePayload, error) {
|
||||
userID, ok := GetUserID(ctx)
|
||||
if !ok {
|
||||
return &db.UserAccount{}, fmt.Errorf("internal server error")
|
||||
return &MePayload{}, fmt.Errorf("internal server error")
|
||||
}
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
log.WithFields(log.Fields{"userID": userID}).Warning("can not find user for me query")
|
||||
return &db.UserAccount{}, nil
|
||||
return &MePayload{}, nil
|
||||
} else if err != nil {
|
||||
return &db.UserAccount{}, err
|
||||
return &MePayload{}, err
|
||||
}
|
||||
return &user, err
|
||||
var projectRoles []ProjectRole
|
||||
projects, err := r.Repository.GetProjectRolesForUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return &MePayload{}, err
|
||||
}
|
||||
for _, project := range projects {
|
||||
projectRoles = append(projectRoles, ProjectRole{ProjectID: project.ProjectID, RoleCode: ConvertToRoleCode("admin")})
|
||||
// projectRoles = append(projectRoles, ProjectRole{ProjectID: project.ProjectID, RoleCode: ConvertToRoleCode(project.RoleCode)})
|
||||
}
|
||||
var teamRoles []TeamRole
|
||||
teams, err := r.Repository.GetTeamRolesForUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return &MePayload{}, err
|
||||
}
|
||||
for _, team := range teams {
|
||||
// teamRoles = append(teamRoles, TeamRole{TeamID: team.TeamID, RoleCode: ConvertToRoleCode(team.RoleCode)})
|
||||
teamRoles = append(teamRoles, TeamRole{TeamID: team.TeamID, RoleCode: ConvertToRoleCode("admin")})
|
||||
}
|
||||
return &MePayload{User: &user, TeamRoles: teamRoles, ProjectRoles: projectRoles}, err
|
||||
}
|
||||
|
||||
func (r *refreshTokenResolver) ID(ctx context.Context, obj *db.RefreshToken) (uuid.UUID, error) {
|
||||
@ -1171,34 +1149,8 @@ func (r *teamResolver) ID(ctx context.Context, obj *db.Team) (uuid.UUID, error)
|
||||
}
|
||||
|
||||
func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, error) {
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, obj.Owner)
|
||||
members := []Member{}
|
||||
log.WithFields(log.Fields{"teamID": obj.TeamID}).Info("getting members")
|
||||
if err == sql.ErrNoRows {
|
||||
return members, nil
|
||||
}
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account by ID")
|
||||
return members, err
|
||||
}
|
||||
ownedList, err := GetOwnedList(ctx, r.Repository, user)
|
||||
if err != nil {
|
||||
return members, err
|
||||
}
|
||||
memberList, err := GetMemberList(ctx, r.Repository, user)
|
||||
if err != nil {
|
||||
return members, err
|
||||
}
|
||||
|
||||
var url *string
|
||||
if user.ProfileAvatarUrl.Valid {
|
||||
url = &user.ProfileAvatarUrl.String
|
||||
}
|
||||
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
|
||||
members = append(members, Member{
|
||||
ID: obj.Owner, FullName: user.FullName, ProfileIcon: profileIcon, Username: user.Username,
|
||||
Owned: ownedList, Member: memberList, Role: &db.Role{Code: "owner", Name: "Owner"},
|
||||
})
|
||||
teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get project members for project id")
|
||||
@ -1206,7 +1158,7 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
|
||||
}
|
||||
|
||||
for _, teamMember := range teamMembers {
|
||||
user, err = r.Repository.GetUserAccountByID(ctx, teamMember.UserID)
|
||||
user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("get user account by ID")
|
||||
return members, err
|
||||
@ -1262,15 +1214,7 @@ func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *db.UserAccou
|
||||
}
|
||||
|
||||
func (r *userAccountResolver) Owned(ctx context.Context, obj *db.UserAccount) (*OwnedList, error) {
|
||||
ownedTeams, err := r.Repository.GetOwnedTeamsForUserID(ctx, obj.UserID)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &OwnedList{}, err
|
||||
}
|
||||
ownedProjects, err := r.Repository.GetOwnedProjectsForUserID(ctx, obj.UserID)
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return &OwnedList{}, err
|
||||
}
|
||||
return &OwnedList{Teams: ownedTeams, Projects: ownedProjects}, nil
|
||||
return &OwnedList{}, nil // TODO(jordanknott)
|
||||
}
|
||||
|
||||
func (r *userAccountResolver) Member(ctx context.Context, obj *db.UserAccount) (*MemberList, error) {
|
||||
@ -1330,7 +1274,9 @@ func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
|
||||
func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} }
|
||||
|
||||
// TaskChecklistItem returns TaskChecklistItemResolver implementation.
|
||||
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver { return &taskChecklistItemResolver{r} }
|
||||
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver {
|
||||
return &taskChecklistItemResolver{r}
|
||||
}
|
||||
|
||||
// TaskGroup returns TaskGroupResolver implementation.
|
||||
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }
|
||||
|
@ -97,7 +97,6 @@ type Project {
|
||||
createdAt: Time!
|
||||
name: String!
|
||||
team: Team!
|
||||
owner: Member!
|
||||
taskGroups: [TaskGroup!]!
|
||||
members: [Member!]!
|
||||
labels: [ProjectLabel!]!
|
||||
|
@ -1,3 +1,23 @@
|
||||
enum RoleLevel {
|
||||
ADMIN
|
||||
MEMBER
|
||||
}
|
||||
|
||||
enum ActionLevel {
|
||||
ORG
|
||||
TEAM
|
||||
PROJECT
|
||||
}
|
||||
|
||||
enum ObjectType {
|
||||
ORG
|
||||
TEAM
|
||||
PROJECT
|
||||
TASK
|
||||
}
|
||||
|
||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
organizations: [Organization!]!
|
||||
users: [UserAccount!]!
|
||||
@ -9,11 +29,27 @@ type Query {
|
||||
teams: [Team!]!
|
||||
labelColors: [LabelColor!]!
|
||||
taskGroups: [TaskGroup!]!
|
||||
me: UserAccount!
|
||||
me: MePayload!
|
||||
}
|
||||
|
||||
type Mutation
|
||||
|
||||
type TeamRole {
|
||||
teamID: UUID!
|
||||
roleCode: RoleCode!
|
||||
}
|
||||
|
||||
type ProjectRole {
|
||||
projectID: UUID!
|
||||
roleCode: RoleCode!
|
||||
}
|
||||
|
||||
type MePayload {
|
||||
user: UserAccount!
|
||||
teamRoles: [TeamRole!]!
|
||||
projectRoles: [ProjectRole!]!
|
||||
}
|
||||
|
||||
input ProjectsFilter {
|
||||
teamID: UUID
|
||||
}
|
||||
@ -23,7 +59,7 @@ input FindUser {
|
||||
}
|
||||
|
||||
input FindProject {
|
||||
projectId: String!
|
||||
projectID: UUID!
|
||||
}
|
||||
|
||||
input FindTask {
|
||||
|
@ -1,7 +1,9 @@
|
||||
extend type Mutation {
|
||||
createProject(input: NewProject!): Project!
|
||||
deleteProject(input: DeleteProject!): DeleteProjectPayload!
|
||||
updateProjectName(input: UpdateProjectName): Project!
|
||||
createProject(input: NewProject!): Project! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
deleteProject(input: DeleteProject!):
|
||||
DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectName(input: UpdateProjectName):
|
||||
Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewProject {
|
||||
|
@ -1,9 +1,14 @@
|
||||
extend type Mutation {
|
||||
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
|
||||
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
|
||||
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
|
||||
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
|
||||
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
|
||||
createProjectLabel(input: NewProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteProjectLabel(input: DeleteProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabel(input: UpdateProjectLabel!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabelName(input: UpdateProjectLabelName!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectLabelColor(input: UpdateProjectLabelColor!):
|
||||
ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewProjectLabel {
|
||||
|
@ -1,8 +1,10 @@
|
||||
extend type Mutation {
|
||||
createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
|
||||
deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
|
||||
updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
|
||||
setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
|
||||
createProjectMember(input: CreateProjectMember!):
|
||||
CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteProjectMember(input: DeleteProjectMember!):
|
||||
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateProjectMemberRole(input: UpdateProjectMemberRole!):
|
||||
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input CreateProjectMember {
|
||||
@ -36,13 +38,3 @@ type UpdateProjectMemberRolePayload {
|
||||
ok: Boolean!
|
||||
member: Member!
|
||||
}
|
||||
|
||||
input SetProjectOwner {
|
||||
projectID: UUID!
|
||||
ownerID: UUID!
|
||||
}
|
||||
type SetProjectOwnerPayload {
|
||||
ok: Boolean!
|
||||
prevOwner: Member!
|
||||
newOwner: Member!
|
||||
}
|
||||
|
@ -1,15 +1,24 @@
|
||||
extend type Mutation {
|
||||
createTask(input: NewTask!): Task!
|
||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||
createTask(input: NewTask!):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteTask(input: DeleteTaskInput!):
|
||||
DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
|
||||
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
||||
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
|
||||
updateTaskName(input: UpdateTaskName!): Task!
|
||||
setTaskComplete(input: SetTaskComplete!): Task!
|
||||
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
|
||||
updateTaskDescription(input: UpdateTaskDescriptionInput!):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskLocation(input: NewTaskLocation!):
|
||||
UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskName(input: UpdateTaskName!):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
setTaskComplete(input: SetTaskComplete!):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskDueDate(input: UpdateTaskDueDate!):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
|
||||
assignTask(input: AssignTaskInput): Task!
|
||||
unassignTask(input: UnassignTaskInput): Task!
|
||||
assignTask(input: AssignTaskInput):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
unassignTask(input: UnassignTaskInput):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewTask {
|
||||
|
@ -1,14 +1,23 @@
|
||||
extend type Mutation {
|
||||
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
|
||||
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
|
||||
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
|
||||
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
|
||||
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
|
||||
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
|
||||
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
|
||||
createTaskChecklist(input: CreateTaskChecklist!):
|
||||
TaskChecklist! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteTaskChecklist(input: DeleteTaskChecklist!):
|
||||
DeleteTaskChecklistPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskChecklistName(input: UpdateTaskChecklistName!):
|
||||
TaskChecklist! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
createTaskChecklistItem(input: CreateTaskChecklistItem!):
|
||||
TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!):
|
||||
TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!):
|
||||
TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!):
|
||||
DeleteTaskChecklistItemPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskChecklistLocation(input: UpdateTaskChecklistLocation!):
|
||||
UpdateTaskChecklistLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskChecklistItemLocation(input: UpdateTaskChecklistItemLocation!):
|
||||
UpdateTaskChecklistItemLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
|
||||
updateTaskChecklistLocation(input: UpdateTaskChecklistLocation!): UpdateTaskChecklistLocationPayload!
|
||||
updateTaskChecklistItemLocation(input: UpdateTaskChecklistItemLocation!): UpdateTaskChecklistItemLocationPayload!
|
||||
}
|
||||
|
||||
input UpdateTaskChecklistItemLocation {
|
||||
|
@ -1,8 +1,12 @@
|
||||
extend type Mutation {
|
||||
createTaskGroup(input: NewTaskGroup!): TaskGroup!
|
||||
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
|
||||
updateTaskGroupName(input: UpdateTaskGroupName!): TaskGroup!
|
||||
deleteTaskGroup(input: DeleteTaskGroupInput!): DeleteTaskGroupPayload!
|
||||
createTaskGroup(input: NewTaskGroup!):
|
||||
TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskGroupLocation(input: NewTaskGroupLocation!):
|
||||
TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
updateTaskGroupName(input: UpdateTaskGroupName!):
|
||||
TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
deleteTaskGroup(input: DeleteTaskGroupInput!):
|
||||
DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
}
|
||||
|
||||
input NewTaskGroupLocation {
|
||||
|
@ -16,7 +16,11 @@ type ToggleTaskLabelPayload {
|
||||
task: Task!
|
||||
}
|
||||
extend type Mutation {
|
||||
addTaskLabel(input: AddTaskLabelInput): Task!
|
||||
removeTaskLabel(input: RemoveTaskLabelInput): Task!
|
||||
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
|
||||
addTaskLabel(input: AddTaskLabelInput):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
removeTaskLabel(input: RemoveTaskLabelInput):
|
||||
Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
toggleTaskLabel(input: ToggleTaskLabelInput!):
|
||||
ToggleTaskLabelPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
extend type Mutation {
|
||||
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
|
||||
createTeam(input: NewTeam!): Team!
|
||||
deleteTeam(input: DeleteTeam!):
|
||||
DeleteTeamPayload! @hasRole(roles:[ ADMIN], level: TEAM, type: TEAM)
|
||||
createTeam(input: NewTeam!):
|
||||
Team! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
}
|
||||
|
||||
input NewTeam {
|
||||
|
@ -1,8 +1,11 @@
|
||||
extend type Mutation {
|
||||
setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
|
||||
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
|
||||
updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
|
||||
deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload!
|
||||
createTeamMember(input: CreateTeamMember!):
|
||||
CreateTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
updateTeamMemberRole(input: UpdateTeamMemberRole!):
|
||||
UpdateTeamMemberRolePayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
deleteTeamMember(input: DeleteTeamMember!):
|
||||
DeleteTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
|
||||
|
||||
}
|
||||
|
||||
input DeleteTeamMember {
|
||||
@ -35,16 +38,6 @@ input UpdateTeamMemberRole {
|
||||
|
||||
type UpdateTeamMemberRolePayload {
|
||||
ok: Boolean!
|
||||
teamID: UUID!
|
||||
member: Member!
|
||||
}
|
||||
|
||||
input SetTeamOwner {
|
||||
teamID: UUID!
|
||||
userID: UUID!
|
||||
}
|
||||
|
||||
type SetTeamOwnerPayload {
|
||||
ok: Boolean!
|
||||
prevOwner: Member!
|
||||
newOwner: Member!
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
extend type Mutation {
|
||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||
createUserAccount(input: NewUserAccount!): UserAccount!
|
||||
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
|
||||
createUserAccount(input: NewUserAccount!):
|
||||
UserAccount! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
deleteUserAccount(input: DeleteUserAccount!):
|
||||
DeleteUserAccountPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
clearProfileAvatar: UserAccount!
|
||||
|
||||
updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
|
||||
updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
|
||||
updateUserRole(input: UpdateUserRole!):
|
||||
UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
||||
}
|
||||
|
||||
input UpdateUserPassword {
|
||||
|
@ -62,7 +62,7 @@ func (h *TaskcafeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Req
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.InstallOnly)
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.InstallOnly, user.RoleCode)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
@ -100,6 +100,13 @@ func (h *TaskcafeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.repo.GetUserAccountByID(r.Context(), token.UserID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("user retrieve failure")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
refreshCreatedAt := time.Now().UTC()
|
||||
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
|
||||
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{token.UserID, refreshCreatedAt, refreshExpiresAt})
|
||||
@ -109,7 +116,7 @@ func (h *TaskcafeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Req
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
accessTokenString, err := auth.NewAccessToken(token.UserID.String(), auth.Unrestricted)
|
||||
accessTokenString, err := auth.NewAccessToken(token.UserID.String(), auth.Unrestricted, user.RoleCode)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
@ -175,7 +182,7 @@ func (h *TaskcafeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
|
||||
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt})
|
||||
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted)
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted, user.RoleCode)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
@ -235,7 +242,7 @@ func (h *TaskcafeHandler) InstallHandler(w http.ResponseWriter, r *http.Request)
|
||||
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
|
||||
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt})
|
||||
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted)
|
||||
accessTokenString, err := auth.NewAccessToken(user.UserID.String(), auth.Unrestricted, user.RoleCode)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), "userID", userID)
|
||||
ctx = context.WithValue(ctx, "restricted_mode", accessClaims.Restricted)
|
||||
ctx = context.WithValue(ctx, "org_role", accessClaims.OrgRole)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
|
Reference in New Issue
Block a user