2021-10-26 00:42:57 +02:00
|
|
|
package graph
|
|
|
|
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
|
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/jordanknott/taskcafe/internal/db"
|
|
|
|
"github.com/jordanknott/taskcafe/internal/logger"
|
|
|
|
"github.com/jordanknott/taskcafe/internal/utils"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.UUID, error) {
|
|
|
|
return obj.LabelColorID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*db.ProjectLabel, error) {
|
|
|
|
createdAt := time.Now().UTC()
|
|
|
|
|
|
|
|
var name sql.NullString
|
|
|
|
if input.Name != nil {
|
|
|
|
name = sql.NullString{
|
|
|
|
*input.Name,
|
|
|
|
true,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name = sql.NullString{
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
projectLabel, err := r.Repository.CreateProjectLabel(ctx, db.CreateProjectLabelParams{input.ProjectID, input.LabelColorID, createdAt, name})
|
|
|
|
return &projectLabel, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*db.ProjectLabel, error) {
|
|
|
|
label, err := r.Repository.GetProjectLabelByID(ctx, input.ProjectLabelID)
|
|
|
|
if err != nil {
|
|
|
|
return &db.ProjectLabel{}, err
|
|
|
|
}
|
|
|
|
err = r.Repository.DeleteProjectLabelByID(ctx, input.ProjectLabelID)
|
|
|
|
return &label, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error) {
|
|
|
|
label, err := r.Repository.UpdateProjectLabel(ctx, db.UpdateProjectLabelParams{ProjectLabelID: input.ProjectLabelID, LabelColorID: input.LabelColorID, Name: sql.NullString{String: input.Name, Valid: true}})
|
|
|
|
return &label, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) UpdateProjectLabelName(ctx context.Context, input UpdateProjectLabelName) (*db.ProjectLabel, error) {
|
|
|
|
label, err := r.Repository.UpdateProjectLabelName(ctx, db.UpdateProjectLabelNameParams{ProjectLabelID: input.ProjectLabelID, Name: sql.NullString{String: input.Name, Valid: true}})
|
|
|
|
return &label, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) UpdateProjectLabelColor(ctx context.Context, input UpdateProjectLabelColor) (*db.ProjectLabel, error) {
|
|
|
|
label, err := r.Repository.UpdateProjectLabelColor(ctx, db.UpdateProjectLabelColorParams{ProjectLabelID: input.ProjectLabelID, LabelColorID: input.LabelColorID})
|
|
|
|
return &label, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input InviteProjectMembers) (*InviteProjectMembersPayload, error) {
|
|
|
|
members := []Member{}
|
|
|
|
invitedMembers := []InvitedMember{}
|
|
|
|
for _, invitedMember := range input.Members {
|
|
|
|
if invitedMember.Email != nil && invitedMember.UserID != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
|
|
|
|
Message: "Both email and userID can not be used to invite a project member",
|
|
|
|
Extensions: map[string]interface{}{
|
|
|
|
"code": "403",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else if invitedMember.Email == nil && invitedMember.UserID == nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
|
|
|
|
Message: "Either email or userID must be set to invite a project member",
|
|
|
|
Extensions: map[string]interface{}{
|
|
|
|
"code": "403",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if invitedMember.UserID != nil {
|
|
|
|
// Invite by user ID
|
|
|
|
addedAt := time.Now().UTC()
|
|
|
|
_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *invitedMember.UserID, AddedAt: addedAt, RoleCode: "member"})
|
|
|
|
if err != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
user, err := r.Repository.GetUserAccountByID(ctx, *invitedMember.UserID)
|
|
|
|
if err != nil && err != sql.ErrNoRows {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, 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: *invitedMember.UserID, ProjectID: input.ProjectID})
|
|
|
|
if err != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
members = append(members, Member{
|
|
|
|
ID: *invitedMember.UserID,
|
|
|
|
FullName: user.FullName,
|
|
|
|
Username: user.Username,
|
|
|
|
ProfileIcon: profileIcon,
|
|
|
|
Role: &db.Role{Code: role.Code, Name: role.Name},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Invite by email
|
|
|
|
|
|
|
|
// if invited user does not exist, create entry
|
|
|
|
invitedUser, err := r.Repository.GetInvitedUserByEmail(ctx, *invitedMember.Email)
|
|
|
|
now := time.Now().UTC()
|
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
invitedUser, err = r.Repository.CreateInvitedUser(ctx, *invitedMember.Email)
|
|
|
|
if err != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
confirmToken, err := r.Repository.CreateConfirmToken(ctx, *invitedMember.Email)
|
|
|
|
if err != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
invite := utils.EmailInvite{To: *invitedMember.Email, FullName: *invitedMember.Email, ConfirmToken: confirmToken.ConfirmTokenID.String()}
|
2021-10-27 05:10:29 +02:00
|
|
|
err = utils.SendEmailInvite(r.AppConfig.Email, invite)
|
2021-10-26 00:42:57 +02:00
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("issue sending email")
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.Repository.CreateInvitedProjectMember(ctx, db.CreateInvitedProjectMemberParams{
|
|
|
|
ProjectID: input.ProjectID,
|
|
|
|
UserAccountInvitedID: invitedUser.UserAccountInvitedID,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return &InviteProjectMembersPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
logger.New(ctx).Info("adding invited member")
|
|
|
|
invitedMembers = append(invitedMembers, InvitedMember{Email: *invitedMember.Email, InvitedOn: now})
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &InviteProjectMembersPayload{Ok: false, ProjectID: input.ProjectID, Members: members, InvitedMembers: invitedMembers}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) {
|
|
|
|
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteProjectMemberPayload{Ok: false}, 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: input.UserID, ProjectID: input.ProjectID})
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteProjectMemberPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
err = r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{UserID: input.UserID, ProjectID: input.ProjectID})
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteProjectMemberPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
return &DeleteProjectMemberPayload{Ok: true, Member: &Member{
|
|
|
|
ID: input.UserID,
|
|
|
|
FullName: user.FullName,
|
|
|
|
ProfileIcon: profileIcon,
|
|
|
|
Role: &db.Role{Code: role.Code, Name: role.Name},
|
|
|
|
}, ProjectID: input.ProjectID}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) {
|
|
|
|
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("get user account")
|
|
|
|
return &UpdateProjectMemberRolePayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
_, err = r.Repository.UpdateProjectMemberRole(ctx, db.UpdateProjectMemberRoleParams{ProjectID: input.ProjectID,
|
|
|
|
UserID: input.UserID, RoleCode: input.RoleCode.String()})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("update project member role")
|
|
|
|
return &UpdateProjectMemberRolePayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: input.ProjectID})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("get role for project member")
|
|
|
|
return &UpdateProjectMemberRolePayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
var url *string
|
|
|
|
if user.ProfileAvatarUrl.Valid {
|
|
|
|
url = &user.ProfileAvatarUrl.String
|
|
|
|
}
|
|
|
|
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
|
|
|
|
if user.ProfileAvatarUrl.Valid {
|
|
|
|
url = &user.ProfileAvatarUrl.String
|
|
|
|
}
|
|
|
|
member := Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
|
|
|
|
Role: &db.Role{Code: role.Code, Name: role.Name},
|
|
|
|
}
|
|
|
|
return &UpdateProjectMemberRolePayload{Ok: true, Member: &member}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) DeleteInvitedProjectMember(ctx context.Context, input DeleteInvitedProjectMember) (*DeleteInvitedProjectMemberPayload, error) {
|
|
|
|
member, err := r.Repository.GetProjectMemberInvitedIDByEmail(ctx, input.Email)
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteInvitedProjectMemberPayload{}, err
|
|
|
|
}
|
|
|
|
err = r.Repository.DeleteInvitedProjectMemberByID(ctx, member.ProjectMemberInvitedID)
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteInvitedProjectMemberPayload{}, err
|
|
|
|
}
|
|
|
|
return &DeleteInvitedProjectMemberPayload{
|
|
|
|
InvitedMember: &InvitedMember{Email: member.Email, InvitedOn: member.InvitedOn},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*db.Project, error) {
|
|
|
|
userID, ok := GetUserID(ctx)
|
|
|
|
if !ok {
|
|
|
|
return &db.Project{}, errors.New("user id is missing")
|
|
|
|
}
|
|
|
|
createdAt := time.Now().UTC()
|
|
|
|
logger.New(ctx).WithFields(log.Fields{"name": input.Name, "teamID": input.TeamID}).Info("creating new project")
|
|
|
|
var project db.Project
|
|
|
|
var err error
|
|
|
|
if input.TeamID == nil {
|
|
|
|
project, err = r.Repository.CreatePersonalProject(ctx, db.CreatePersonalProjectParams{
|
|
|
|
CreatedAt: createdAt,
|
|
|
|
Name: input.Name,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("error while creating project")
|
|
|
|
return &db.Project{}, err
|
|
|
|
}
|
|
|
|
logger.New(ctx).WithField("projectID", project.ProjectID).Info("creating personal project link")
|
|
|
|
} else {
|
|
|
|
project, err = r.Repository.CreateTeamProject(ctx, db.CreateTeamProjectParams{
|
|
|
|
CreatedAt: createdAt,
|
|
|
|
Name: input.Name,
|
|
|
|
TeamID: *input.TeamID,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("error while creating project")
|
|
|
|
return &db.Project{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: project.ProjectID, UserID: userID, AddedAt: createdAt, RoleCode: "admin"})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("error while creating initial project member")
|
|
|
|
return &db.Project{}, err
|
|
|
|
}
|
|
|
|
return &project, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error) {
|
|
|
|
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteProjectPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
err = r.Repository.DeleteProjectByID(ctx, input.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return &DeleteProjectPayload{Ok: false}, err
|
|
|
|
}
|
|
|
|
return &DeleteProjectPayload{Project: &project, Ok: true}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*db.Project, error) {
|
|
|
|
project, err := r.Repository.UpdateProjectNameByID(ctx, db.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name})
|
|
|
|
if err != nil {
|
|
|
|
return &db.Project{}, err
|
|
|
|
}
|
|
|
|
return &project, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *mutationResolver) ToggleProjectVisibility(ctx context.Context, input ToggleProjectVisibility) (*ToggleProjectVisibilityPayload, error) {
|
|
|
|
if input.IsPublic {
|
|
|
|
project, err := r.Repository.SetPublicOn(ctx, db.SetPublicOnParams{ProjectID: input.ProjectID, PublicOn: sql.NullTime{Valid: true, Time: time.Now().UTC()}})
|
|
|
|
return &ToggleProjectVisibilityPayload{Project: &project}, err
|
|
|
|
}
|
|
|
|
project, err := r.Repository.SetPublicOn(ctx, db.SetPublicOnParams{ProjectID: input.ProjectID, PublicOn: sql.NullTime{Valid: false, Time: time.Time{}}})
|
|
|
|
return &ToggleProjectVisibilityPayload{Project: &project}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) ID(ctx context.Context, obj *db.Project) (uuid.UUID, error) {
|
|
|
|
return obj.ProjectID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) Team(ctx context.Context, obj *db.Project) (*db.Team, error) {
|
|
|
|
team, err := r.Repository.GetTeamByID(ctx, obj.TeamID)
|
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
logger.New(ctx).WithFields(log.Fields{"teamID": obj.TeamID, "projectID": obj.ProjectID}).WithError(err).Error("issue while getting team for project")
|
|
|
|
return &team, err
|
|
|
|
}
|
|
|
|
return &team, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) TaskGroups(ctx context.Context, obj *db.Project) ([]db.TaskGroup, error) {
|
|
|
|
return r.Repository.GetTaskGroupsForProject(ctx, obj.ProjectID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Member, error) {
|
|
|
|
members := []Member{}
|
|
|
|
projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("get project members for project id")
|
|
|
|
return members, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, projectMember := range projectMembers {
|
|
|
|
user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("get user account by ID")
|
|
|
|
return members, err
|
|
|
|
}
|
|
|
|
var url *string
|
|
|
|
if user.ProfileAvatarUrl.Valid {
|
|
|
|
url = &user.ProfileAvatarUrl.String
|
|
|
|
}
|
|
|
|
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: obj.ProjectID})
|
|
|
|
if err != nil {
|
|
|
|
logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
|
|
|
|
return members, err
|
|
|
|
}
|
|
|
|
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
|
|
|
|
members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
|
|
|
|
Username: user.Username, Role: &db.Role{Code: role.Code, Name: role.Name},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return members, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) InvitedMembers(ctx context.Context, obj *db.Project) ([]InvitedMember, error) {
|
|
|
|
members, err := r.Repository.GetInvitedMembersForProjectID(ctx, obj.ProjectID)
|
|
|
|
if err != nil && err == sql.ErrNoRows {
|
|
|
|
return []InvitedMember{}, nil
|
|
|
|
}
|
|
|
|
invited := []InvitedMember{}
|
|
|
|
for _, member := range members {
|
|
|
|
invited = append(invited, InvitedMember{Email: member.Email, InvitedOn: member.InvitedOn})
|
|
|
|
}
|
|
|
|
return invited, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) PublicOn(ctx context.Context, obj *db.Project) (*time.Time, error) {
|
|
|
|
if obj.PublicOn.Valid {
|
|
|
|
return &obj.PublicOn.Time, nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) Permission(ctx context.Context, obj *db.Project) (*ProjectPermission, error) {
|
|
|
|
panic(fmt.Errorf("not implemented"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectResolver) Labels(ctx context.Context, obj *db.Project) ([]db.ProjectLabel, error) {
|
|
|
|
labels, err := r.Repository.GetProjectLabelsForProject(ctx, obj.ProjectID)
|
|
|
|
return labels, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectLabelResolver) ID(ctx context.Context, obj *db.ProjectLabel) (uuid.UUID, error) {
|
|
|
|
return obj.ProjectLabelID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectLabelResolver) LabelColor(ctx context.Context, obj *db.ProjectLabel) (*db.LabelColor, error) {
|
|
|
|
labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID)
|
|
|
|
if err != nil {
|
|
|
|
return &db.LabelColor{}, err
|
|
|
|
}
|
|
|
|
return &labelColor, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *projectLabelResolver) Name(ctx context.Context, obj *db.ProjectLabel) (*string, error) {
|
|
|
|
var name *string
|
|
|
|
if obj.Name.Valid {
|
|
|
|
name = &obj.Name.String
|
|
|
|
}
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LabelColor returns LabelColorResolver implementation.
|
|
|
|
func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{r} }
|
|
|
|
|
|
|
|
// Project returns ProjectResolver implementation.
|
|
|
|
func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} }
|
|
|
|
|
|
|
|
// ProjectLabel returns ProjectLabelResolver implementation.
|
|
|
|
func (r *Resolver) ProjectLabel() ProjectLabelResolver { return &projectLabelResolver{r} }
|
|
|
|
|
|
|
|
type labelColorResolver struct{ *Resolver }
|
|
|
|
type projectResolver struct{ *Resolver }
|
|
|
|
type projectLabelResolver struct{ *Resolver }
|