feature: add user project count to Admin component

This commit is contained in:
Jordan Knott 2020-07-17 19:40:05 -05:00
parent ccaa97e2bb
commit 68fa7aef94
23 changed files with 1140 additions and 140 deletions

View File

@ -12,7 +12,7 @@ import {
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
import styled from 'styled-components'; import styled from 'styled-components';
import Button from 'shared/components/Button'; import Button from 'shared/components/Button';
import { useForm } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer'; import produce from 'immer';
import updateApolloCache from 'shared/utils/cache'; import updateApolloCache from 'shared/utils/cache';
@ -45,14 +45,19 @@ const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
</DeleteUserWrapper> </DeleteUserWrapper>
); );
}; };
type RoleCodeOption = {
label: string;
value: string;
};
type CreateUserData = { type CreateUserData = {
email: string; email: string;
username: string; username: string;
fullName: string; fullName: string;
initials: string; initials: string;
password: string; password: string;
roleCode: string; roleCode: RoleCodeOption;
}; };
const CreateUserForm = styled.form` const CreateUserForm = styled.form`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -78,11 +83,11 @@ type AddUserPopupProps = {
}; };
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => { const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors, setValue } = useForm<CreateUserData>(); const { register, handleSubmit, errors, setValue, control } = useForm<CreateUserData>();
const [role, setRole] = useState<string | null>(null);
register({ name: 'roleCode' }, { required: true });
console.log(errors);
const createUser = (data: CreateUserData) => { const createUser = (data: CreateUserData) => {
console.log(data);
onAddUser(data); onAddUser(data);
}; };
return ( return (
@ -106,19 +111,23 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
variant="alternate" variant="alternate"
ref={register({ required: 'Email is required' })} ref={register({ required: 'Email is required' })}
/> />
<Controller
control={control}
name="roleCode"
rules={{ required: 'Role is required' }}
render={({ onChange, onBlur, value }) => (
<Select <Select
label="Role" label="Role"
value={role} value={value}
onChange={onChange}
options={[ options={[
{ label: 'Admin', value: 'admin' }, { label: 'Admin', value: 'admin' },
{ label: 'Member', value: 'member' }, { label: 'Member', value: 'member' },
]} ]}
onChange={newRole => {
setRole(newRole);
setValue('roleCode', newRole.value);
}}
/> />
{errors.email && <InputError>{errors.email.message}</InputError>} )}
/>
{errors.roleCode && errors.roleCode.value && <InputError>{errors.roleCode.value.message}</InputError>}
<AddUserInput <AddUserInput
floatingLabel floatingLabel
width="100%" width="100%"
@ -146,6 +155,7 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
id="password" id="password"
name="password" name="password"
variant="alternate" variant="alternate"
type="password"
ref={register({ required: 'Password is required' })} ref={register({ required: 'Password is required' })}
/> />
{errors.password && <InputError>{errors.password.message}</InputError>} {errors.password && <InputError>{errors.password.message}</InputError>}
@ -196,7 +206,7 @@ const AdminRoute = () => {
<> <>
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} /> <GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
<Admin <Admin
initialTab={1} initialTab={0}
users={data.users} users={data.users}
onInviteUser={() => {}} onInviteUser={() => {}}
onUpdateUserPassword={(user, password) => { onUpdateUserPassword={(user, password) => {
@ -223,7 +233,8 @@ const AdminRoute = () => {
<Popup tab={0} title="Add member" onClose={() => hidePopup()}> <Popup tab={0} title="Add member" onClose={() => hidePopup()}>
<AddUserPopup <AddUserPopup
onAddUser={user => { onAddUser={user => {
createUser({ variables: { ...user } }); const { roleCode, ...userData } = user;
createUser({ variables: { ...userData, roleCode: roleCode.value } });
hidePopup(); hidePopup();
}} }}
/> />

View File

@ -87,11 +87,12 @@ const CreateChecklistPopup: React.FC<CreateChecklistPopupProps> = ({ onCreateChe
const createUser = (data: CreateChecklistData) => { const createUser = (data: CreateChecklistData) => {
onCreateChecklist(data); onCreateChecklist(data);
}; };
console.log(errors);
return ( return (
<CreateChecklistForm onSubmit={handleSubmit(createUser)}> <CreateChecklistForm onSubmit={handleSubmit(createUser)}>
<CreateChecklistInput <CreateChecklistInput
floatingLabel floatingLabel
autoFocus
autoSelect
defaultValue="Checklist" defaultValue="Checklist"
width="100%" width="100%"
label="Name" label="Name"
@ -437,7 +438,7 @@ const Details: React.FC<DetailsProps> = ({
showPopup( showPopup(
$target, $target,
<Popup <Popup
title={'Add checklist'} title="Add checklist"
tab={0} tab={0}
onClose={() => { onClose={() => {
hidePopup(); hidePopup();

View File

@ -178,6 +178,8 @@ const Project = () => {
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
console.log(cache);
console.log(response);
draftCache.findProject.members = cache.findProject.members.filter( draftCache.findProject.members = cache.findProject.members.filter(
m => m.id !== response.data.deleteProjectMember.member.id, m => m.id !== response.data.deleteProjectMember.member.id,
); );

View File

@ -159,8 +159,8 @@ export const RemoveMemberButton = styled(Button)`
`; `;
type TeamRoleManagerPopupProps = { type TeamRoleManagerPopupProps = {
currentUserID: string; currentUserID: string;
subject: TaskUser; subject: User;
members: Array<TaskUser>; members: Array<User>;
warning?: string | null; warning?: string | null;
canChangeRole: boolean; canChangeRole: boolean;
onChangeRole: (roleCode: RoleCode) => void; onChangeRole: (roleCode: RoleCode) => void;

View File

@ -22,13 +22,19 @@ type Role = {
name: string; name: string;
}; };
type User = { type UserProject = {
id: string; id: string;
fullName: string; name: string;
username: string; };
email: string;
role: Role; type UserTeam = {
profileIcon: ProfileIcon; id: string;
name: string;
};
type RelatedList = {
teams: Array<UserTeam>;
projects: Array<UserProject>;
}; };
type OwnedList = { type OwnedList = {
@ -42,7 +48,12 @@ type TaskUser = {
profileIcon: ProfileIcon; profileIcon: ProfileIcon;
username?: string; username?: string;
role?: Role; role?: Role;
owned?: OwnedList | null; };
type User = TaskUser & {
email?: string;
member: RelatedList;
owned: RelatedList;
}; };
type RefreshTokenResponse = { type RefreshTokenResponse = {

View File

@ -40,6 +40,14 @@ export const Default = () => {
initials: 'JK', initials: 'JK',
url: null, url: null,
}, },
owned: {
teams: [{ id: '1', name: 'Team' }],
projects: [{ id: '2', name: 'Project' }],
},
member: {
teams: [],
projects: [],
},
}, },
]} ]}
onAddUser={action('add user')} onAddUser={action('add user')}

View File

@ -509,7 +509,7 @@ const ListTable: React.FC<ListTableProps> = ({ users, onDeleteUser }) => {
rowSelection="multiple" rowSelection="multiple"
defaultColDef={data.defaultColDef} defaultColDef={data.defaultColDef}
columnDefs={data.columnDefs} columnDefs={data.columnDefs}
rowData={users.map(u => ({ ...u, roleName: u.role.name }))} rowData={users.map(u => ({ ...u, roleName: 'member' }))}
frameworkComponents={data.frameworkComponents} frameworkComponents={data.frameworkComponents}
onFirstDataRendered={params => { onFirstDataRendered={params => {
params.api.sizeColumnsToFit(); params.api.sizeColumnsToFit();
@ -717,7 +717,9 @@ const Admin: React.FC<AdminProps> = ({
</ListActions> </ListActions>
</MemberListHeader> </MemberListHeader>
<MemberList> <MemberList>
{users.map(member => ( {users.map(member => {
const projectTotal = member.owned.projects.length + member.member.projects.length;
return (
<MemberListItem> <MemberListItem>
<MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} /> <MemberProfile showRoleIcons size={32} onMemberProfile={() => {}} member={member} />
<MemberListItemDetails> <MemberListItemDetails>
@ -725,7 +727,7 @@ const Admin: React.FC<AdminProps> = ({
<MemberItemUsername>{`@${member.username}`}</MemberItemUsername> <MemberItemUsername>{`@${member.username}`}</MemberItemUsername>
</MemberListItemDetails> </MemberListItemDetails>
<MemberItemOptions> <MemberItemOptions>
<MemberItemOption variant="flat">On 6 projects</MemberItemOption> <MemberItemOption variant="flat">{`On ${projectTotal} projects`}</MemberItemOption>
<MemberItemOption <MemberItemOption
variant="outline" variant="outline"
onClick={$target => { onClick={$target => {
@ -737,7 +739,7 @@ const Admin: React.FC<AdminProps> = ({
updateUserPassword={(user, password) => { updateUserPassword={(user, password) => {
onUpdateUserPassword(user, password); onUpdateUserPassword(user, password);
}} }}
canChangeRole={member.role && member.role.code !== 'owner'} canChangeRole={(member.role && member.role.code !== 'owner') ?? false}
onChangeRole={roleCode => { onChangeRole={roleCode => {
updateUserRole({ variables: { userID: member.id, roleCode } }); updateUserRole({ variables: { userID: member.id, roleCode } });
}} }}
@ -756,7 +758,8 @@ const Admin: React.FC<AdminProps> = ({
</MemberItemOption> </MemberItemOption>
</MemberItemOptions> </MemberItemOptions>
</MemberListItem> </MemberListItem>
))} );
})}
</MemberList> </MemberList>
</MemberListWrapper> </MemberListWrapper>
</TabContent> </TabContent>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import styled, { css } from 'styled-components/macro'; import styled, { css } from 'styled-components/macro';
const InputWrapper = styled.div<{ width: string }>` const InputWrapper = styled.div<{ width: string }>`
@ -85,6 +85,8 @@ type InputProps = {
icon?: JSX.Element; icon?: JSX.Element;
type?: string; type?: string;
autocomplete?: boolean; autocomplete?: boolean;
autoFocus?: boolean;
autoSelect?: boolean;
id?: string; id?: string;
name?: string; name?: string;
className?: string; className?: string;
@ -92,12 +94,32 @@ type InputProps = {
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void; onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;
}; };
function useCombinedRefs(...refs: any) {
const targetRef = React.useRef();
React.useEffect(() => {
refs.forEach((ref: any) => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
ref.current = targetRef.current;
}
});
}, [refs]);
return targetRef;
}
const Input = React.forwardRef( const Input = React.forwardRef(
( (
{ {
width = 'auto', width = 'auto',
variant = 'normal', variant = 'normal',
type = 'text', type = 'text',
autoFocus = false,
autoSelect = false,
autocomplete, autocomplete,
label, label,
placeholder, placeholder,
@ -111,9 +133,25 @@ const Input = React.forwardRef(
}: InputProps, }: InputProps,
$ref: any, $ref: any,
) => { ) => {
const [hasValue, setHasValue] = useState(false); const [hasValue, setHasValue] = useState(defaultValue !== '');
const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561'; const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561';
const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)'; const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)';
// Merge forwarded ref and internal ref in order to be able to access the ref in the useEffect
// The forwarded ref is not accessible by itself, which is what the innerRef & combined ref is for
// TODO(jordanknott): This is super ugly, find a better approach?
const $innerRef = React.useRef<HTMLInputElement>(null);
const combinedRef: any = useCombinedRefs($ref, $innerRef);
useEffect(() => {
if (combinedRef && combinedRef.current) {
if (autoFocus) {
combinedRef.current.focus();
}
if (autoSelect) {
combinedRef.current.select();
}
}
}, []);
return ( return (
<InputWrapper className={className} width={width}> <InputWrapper className={className} width={width}>
<InputInput <InputInput
@ -121,7 +159,7 @@ const Input = React.forwardRef(
setHasValue((e.currentTarget.value !== '' || floatingLabel) ?? false); setHasValue((e.currentTarget.value !== '' || floatingLabel) ?? false);
}} }}
hasValue={hasValue} hasValue={hasValue}
ref={$ref} ref={combinedRef}
id={id} id={id}
type={type} type={type}
name={name} name={name}

View File

@ -67,7 +67,8 @@ export type Member = {
fullName: Scalars['String']; fullName: Scalars['String'];
username: Scalars['String']; username: Scalars['String'];
profileIcon: ProfileIcon; profileIcon: ProfileIcon;
owned?: Maybe<OwnersList>; owned: OwnedList;
member: MemberList;
}; };
export type RefreshToken = { export type RefreshToken = {
@ -84,6 +85,18 @@ export type Role = {
name: Scalars['String']; name: Scalars['String'];
}; };
export type OwnedList = {
__typename?: 'OwnedList';
teams: Array<Team>;
projects: Array<Project>;
};
export type MemberList = {
__typename?: 'MemberList';
teams: Array<Team>;
projects: Array<Project>;
};
export type UserAccount = { export type UserAccount = {
__typename?: 'UserAccount'; __typename?: 'UserAccount';
id: Scalars['ID']; id: Scalars['ID'];
@ -94,6 +107,8 @@ export type UserAccount = {
role: Role; role: Role;
username: Scalars['String']; username: Scalars['String'];
profileIcon: ProfileIcon; profileIcon: ProfileIcon;
owned: OwnedList;
member: MemberList;
}; };
export type Team = { export type Team = {
@ -1096,6 +1111,24 @@ export type FindProjectQuery = (
), profileIcon: ( ), profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), owned: (
{ __typename?: 'OwnedList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
), member: (
{ __typename?: 'MemberList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
) } ) }
)> } )> }
); );
@ -1611,12 +1644,27 @@ export type GetTeamQuery = (
& { role: ( & { role: (
{ __typename?: 'Role' } { __typename?: 'Role' }
& Pick<Role, 'code' | 'name'> & Pick<Role, 'code' | 'name'>
), owned?: Maybe<( ), profileIcon: (
{ __typename?: 'OwnersList' }
& Pick<OwnersList, 'projects' | 'teams'>
)>, profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), owned: (
{ __typename?: 'OwnedList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
), member: (
{ __typename?: 'MemberList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
) } ) }
)> } )> }
), projects: Array<( ), projects: Array<(
@ -1635,6 +1683,24 @@ export type GetTeamQuery = (
), profileIcon: ( ), profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), owned: (
{ __typename?: 'OwnedList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
), member: (
{ __typename?: 'MemberList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
) } ) }
)> } )> }
); );
@ -1876,6 +1942,24 @@ export type UsersQuery = (
), profileIcon: ( ), profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), owned: (
{ __typename?: 'OwnedList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
), member: (
{ __typename?: 'MemberList' }
& { teams: Array<(
{ __typename?: 'Team' }
& Pick<Team, 'id' | 'name'>
)>, projects: Array<(
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
)> }
) } ) }
)> } )> }
); );
@ -2278,6 +2362,26 @@ export const FindProjectDocument = gql`
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
${TaskFieldsFragmentDoc}`; ${TaskFieldsFragmentDoc}`;
@ -3288,15 +3392,31 @@ export const GetTeamDocument = gql`
code code
name name
} }
owned {
projects
teams
}
profileIcon { profileIcon {
url url
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
projects(input: {teamID: $teamID}) { projects(input: {teamID: $teamID}) {
@ -3321,6 +3441,26 @@ export const GetTeamDocument = gql`
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
`; `;
@ -3834,6 +3974,26 @@ export const UsersDocument = gql`
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
`; `;

View File

@ -59,6 +59,26 @@ query findProject($projectId: String!) {
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
${TASK_FRAGMENT} ${TASK_FRAGMENT}
} }

View File

@ -14,15 +14,31 @@ export const GET_TEAM_QUERY = gql`
code code
name name
} }
owned {
projects
teams
}
profileIcon { profileIcon {
url url
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
projects(input: { teamID: $teamID }) { projects(input: { teamID: $teamID }) {
@ -47,6 +63,26 @@ export const GET_TEAM_QUERY = gql`
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }
`; `;

View File

@ -13,6 +13,26 @@ query users {
initials initials
bgColor bgColor
} }
owned {
teams {
id
name
}
projects {
id
name
}
}
member {
teams {
id
name
}
projects {
id
name
}
}
} }
} }

View File

@ -158,6 +158,66 @@ func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) (
return items, nil return items, nil
} }
const getMemberProjectIDsForUserID = `-- name: GetMemberProjectIDsForUserID :many
SELECT project_id FROM project_member WHERE user_id = $1
`
func (q *Queries) GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) {
rows, err := q.db.QueryContext(ctx, getMemberProjectIDsForUserID, userID)
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 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 const getOwnedTeamProjectsForUserID = `-- name: GetOwnedTeamProjectsForUserID :many
SELECT project_id FROM project WHERE owner = $1 AND team_id = $2 SELECT project_id FROM project WHERE owner = $1 AND team_id = $2
` `

View File

@ -52,7 +52,11 @@ type Querier interface {
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error) GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
GetLabelColors(ctx context.Context) ([]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) 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) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error) GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)

View File

@ -39,3 +39,9 @@ UPDATE project_member SET role_code = $3 WHERE project_id = $1 AND user_id = $2
-- name: GetOwnedTeamProjectsForUserID :many -- name: GetOwnedTeamProjectsForUserID :many
SELECT project_id FROM project WHERE owner = $1 AND team_id = $2; SELECT project_id FROM project WHERE owner = $1 AND team_id = $2;
-- name: GetOwnedProjectsForUserID :many
SELECT * FROM project WHERE owner = $1;
-- name: GetMemberProjectIDsForUserID :many
SELECT project_id FROM project_member WHERE user_id = $1;

View File

@ -15,3 +15,9 @@ SELECT * FROM team WHERE organization_id = $1;
-- name: SetTeamOwner :one -- name: SetTeamOwner :one
UPDATE team SET owner = $2 WHERE team_id = $1 RETURNING *; 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;

View File

@ -81,6 +81,66 @@ func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
return items, nil return items, nil
} }
const getMemberTeamIDsForUserID = `-- name: GetMemberTeamIDsForUserID :many
SELECT team_id FROM team_member WHERE user_id = $1
`
func (q *Queries) GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) {
rows, err := q.db.QueryContext(ctx, getMemberTeamIDsForUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []uuid.UUID
for rows.Next() {
var team_id uuid.UUID
if err := rows.Scan(&team_id); err != nil {
return nil, err
}
items = append(items, team_id)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getOwnedTeamsForUserID = `-- name: GetOwnedTeamsForUserID :many
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE owner = $1
`
func (q *Queries) GetOwnedTeamsForUserID(ctx context.Context, owner uuid.UUID) ([]Team, error) {
rows, err := q.db.QueryContext(ctx, getOwnedTeamsForUserID, owner)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Team
for rows.Next() {
var i Team
if err := rows.Scan(
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&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 getTeamByID = `-- name: GetTeamByID :one const getTeamByID = `-- name: GetTeamByID :one
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE team_id = $1 SELECT team_id, created_at, name, organization_id, owner FROM team WHERE team_id = $1
` `

View File

@ -130,12 +130,18 @@ type ComplexityRoot struct {
Member struct { Member struct {
FullName func(childComplexity int) int FullName func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Member func(childComplexity int) int
Owned func(childComplexity int) int Owned func(childComplexity int) int
ProfileIcon func(childComplexity int) int ProfileIcon func(childComplexity int) int
Role func(childComplexity int) int Role func(childComplexity int) int
Username func(childComplexity int) int Username func(childComplexity int) int
} }
MemberList struct {
Projects func(childComplexity int) int
Teams func(childComplexity int) int
}
Mutation struct { Mutation struct {
AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int
AssignTask func(childComplexity int, input *AssignTaskInput) int AssignTask func(childComplexity int, input *AssignTaskInput) int
@ -194,6 +200,11 @@ type ComplexityRoot struct {
Name func(childComplexity int) int Name func(childComplexity int) int
} }
OwnedList struct {
Projects func(childComplexity int) int
Teams func(childComplexity int) int
}
OwnersList struct { OwnersList struct {
Projects func(childComplexity int) int Projects func(childComplexity int) int
Teams func(childComplexity int) int Teams func(childComplexity int) int
@ -363,6 +374,8 @@ type ComplexityRoot struct {
FullName func(childComplexity int) int FullName func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Initials func(childComplexity int) int Initials func(childComplexity int) int
Member func(childComplexity int) int
Owned func(childComplexity int) int
ProfileIcon func(childComplexity int) int ProfileIcon func(childComplexity int) int
Role func(childComplexity int) int Role func(childComplexity int) int
Username func(childComplexity int) int Username func(childComplexity int) int
@ -501,6 +514,8 @@ type UserAccountResolver interface {
Role(ctx context.Context, obj *db.UserAccount) (*db.Role, error) Role(ctx context.Context, obj *db.UserAccount) (*db.Role, error)
ProfileIcon(ctx context.Context, obj *db.UserAccount) (*ProfileIcon, error) ProfileIcon(ctx context.Context, obj *db.UserAccount) (*ProfileIcon, error)
Owned(ctx context.Context, obj *db.UserAccount) (*OwnedList, error)
Member(ctx context.Context, obj *db.UserAccount) (*MemberList, error)
} }
type executableSchema struct { type executableSchema struct {
@ -749,6 +764,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Member.ID(childComplexity), true return e.complexity.Member.ID(childComplexity), true
case "Member.member":
if e.complexity.Member.Member == nil {
break
}
return e.complexity.Member.Member(childComplexity), true
case "Member.owned": case "Member.owned":
if e.complexity.Member.Owned == nil { if e.complexity.Member.Owned == nil {
break break
@ -777,6 +799,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Member.Username(childComplexity), true return e.complexity.Member.Username(childComplexity), true
case "MemberList.projects":
if e.complexity.MemberList.Projects == nil {
break
}
return e.complexity.MemberList.Projects(childComplexity), true
case "MemberList.teams":
if e.complexity.MemberList.Teams == nil {
break
}
return e.complexity.MemberList.Teams(childComplexity), true
case "Mutation.addTaskLabel": case "Mutation.addTaskLabel":
if e.complexity.Mutation.AddTaskLabel == nil { if e.complexity.Mutation.AddTaskLabel == nil {
break break
@ -1386,6 +1422,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Organization.Name(childComplexity), true return e.complexity.Organization.Name(childComplexity), true
case "OwnedList.projects":
if e.complexity.OwnedList.Projects == nil {
break
}
return e.complexity.OwnedList.Projects(childComplexity), true
case "OwnedList.teams":
if e.complexity.OwnedList.Teams == nil {
break
}
return e.complexity.OwnedList.Teams(childComplexity), true
case "OwnersList.projects": case "OwnersList.projects":
if e.complexity.OwnersList.Projects == nil { if e.complexity.OwnersList.Projects == nil {
break break
@ -2083,6 +2133,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.UserAccount.Initials(childComplexity), true return e.complexity.UserAccount.Initials(childComplexity), true
case "UserAccount.member":
if e.complexity.UserAccount.Member == nil {
break
}
return e.complexity.UserAccount.Member(childComplexity), true
case "UserAccount.owned":
if e.complexity.UserAccount.Owned == nil {
break
}
return e.complexity.UserAccount.Owned(childComplexity), true
case "UserAccount.profileIcon": case "UserAccount.profileIcon":
if e.complexity.UserAccount.ProfileIcon == nil { if e.complexity.UserAccount.ProfileIcon == nil {
break break
@ -2216,7 +2280,8 @@ type Member {
fullName: String! fullName: String!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnersList owned: OwnedList!
member: MemberList!
} }
type RefreshToken { type RefreshToken {
@ -2231,6 +2296,16 @@ type Role {
name: String! name: String!
} }
type OwnedList {
teams: [Team!]!
projects: [Project!]!
}
type MemberList {
teams: [Team!]!
projects: [Project!]!
}
type UserAccount { type UserAccount {
id: ID! id: ID!
email: String! email: String!
@ -2240,6 +2315,8 @@ type UserAccount {
role: Role! role: Role!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnedList!
member: MemberList!
} }
type Team { type Team {
@ -4851,11 +4928,116 @@ func (ec *executionContext) _Member_owned(ctx context.Context, field graphql.Col
return graphql.Null return graphql.Null
} }
if resTmp == nil { if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null return graphql.Null
} }
res := resTmp.(*OwnersList) res := resTmp.(*OwnedList)
fc.Result = res fc.Result = res
return ec.marshalOOwnersList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnersList(ctx, field.Selections, res) return ec.marshalNOwnedList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnedList(ctx, field.Selections, res)
}
func (ec *executionContext) _Member_member(ctx context.Context, field graphql.CollectedField, obj *Member) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Member",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Member, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*MemberList)
fc.Result = res
return ec.marshalNMemberList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMemberList(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberList_teams(ctx context.Context, field graphql.CollectedField, obj *MemberList) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberList",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Teams, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]db.Team)
fc.Result = res
return ec.marshalNTeam2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐTeamᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberList_projects(ctx context.Context, field graphql.CollectedField, obj *MemberList) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberList",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Projects, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]db.Project)
fc.Result = res
return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐProjectᚄ(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -6969,6 +7151,74 @@ func (ec *executionContext) _Organization_name(ctx context.Context, field graphq
return ec.marshalNString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _OwnedList_teams(ctx context.Context, field graphql.CollectedField, obj *OwnedList) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "OwnedList",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Teams, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]db.Team)
fc.Result = res
return ec.marshalNTeam2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐTeamᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _OwnedList_projects(ctx context.Context, field graphql.CollectedField, obj *OwnedList) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "OwnedList",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Projects, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]db.Project)
fc.Result = res
return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋdbᚐProjectᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _OwnersList_projects(ctx context.Context, field graphql.CollectedField, obj *OwnersList) (ret graphql.Marshaler) { func (ec *executionContext) _OwnersList_projects(ctx context.Context, field graphql.CollectedField, obj *OwnersList) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -10418,6 +10668,74 @@ func (ec *executionContext) _UserAccount_profileIcon(ctx context.Context, field
return ec.marshalNProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProfileIcon(ctx, field.Selections, res) return ec.marshalNProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProfileIcon(ctx, field.Selections, res)
} }
func (ec *executionContext) _UserAccount_owned(ctx context.Context, field graphql.CollectedField, obj *db.UserAccount) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "UserAccount",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.UserAccount().Owned(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*OwnedList)
fc.Result = res
return ec.marshalNOwnedList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnedList(ctx, field.Selections, res)
}
func (ec *executionContext) _UserAccount_member(ctx context.Context, field graphql.CollectedField, obj *db.UserAccount) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "UserAccount",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.UserAccount().Member(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*MemberList)
fc.Result = res
return ec.marshalNMemberList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMemberList(ctx, field.Selections, res)
}
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -13265,6 +13583,46 @@ func (ec *executionContext) _Member(ctx context.Context, sel ast.SelectionSet, o
} }
case "owned": case "owned":
out.Values[i] = ec._Member_owned(ctx, field, obj) out.Values[i] = ec._Member_owned(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "member":
out.Values[i] = ec._Member_member(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var memberListImplementors = []string{"MemberList"}
func (ec *executionContext) _MemberList(ctx context.Context, sel ast.SelectionSet, obj *MemberList) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, memberListImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("MemberList")
case "teams":
out.Values[i] = ec._MemberList_teams(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "projects":
out.Values[i] = ec._MemberList_projects(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -13593,6 +13951,38 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
return out return out
} }
var ownedListImplementors = []string{"OwnedList"}
func (ec *executionContext) _OwnedList(ctx context.Context, sel ast.SelectionSet, obj *OwnedList) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, ownedListImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("OwnedList")
case "teams":
out.Values[i] = ec._OwnedList_teams(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "projects":
out.Values[i] = ec._OwnedList_projects(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var ownersListImplementors = []string{"OwnersList"} var ownersListImplementors = []string{"OwnersList"}
func (ec *executionContext) _OwnersList(ctx context.Context, sel ast.SelectionSet, obj *OwnersList) graphql.Marshaler { func (ec *executionContext) _OwnersList(ctx context.Context, sel ast.SelectionSet, obj *OwnersList) graphql.Marshaler {
@ -15001,6 +15391,34 @@ func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionS
} }
return res return res
}) })
case "owned":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._UserAccount_owned(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "member":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._UserAccount_member(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -15645,6 +16063,20 @@ func (ec *executionContext) marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋproje
return ec._Member(ctx, sel, v) return ec._Member(ctx, sel, v)
} }
func (ec *executionContext) marshalNMemberList2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMemberList(ctx context.Context, sel ast.SelectionSet, v MemberList) graphql.Marshaler {
return ec._MemberList(ctx, sel, &v)
}
func (ec *executionContext) marshalNMemberList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐMemberList(ctx context.Context, sel ast.SelectionSet, v *MemberList) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._MemberList(ctx, sel, v)
}
func (ec *executionContext) unmarshalNNewProject2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐNewProject(ctx context.Context, v interface{}) (NewProject, error) { func (ec *executionContext) unmarshalNNewProject2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐNewProject(ctx context.Context, v interface{}) (NewProject, error) {
return ec.unmarshalInputNewProject(ctx, v) return ec.unmarshalInputNewProject(ctx, v)
} }
@ -15722,6 +16154,20 @@ func (ec *executionContext) marshalNOrganization2ᚕgithubᚗcomᚋjordanknott
return ret return ret
} }
func (ec *executionContext) marshalNOwnedList2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnedList(ctx context.Context, sel ast.SelectionSet, v OwnedList) graphql.Marshaler {
return ec._OwnedList(ctx, sel, &v)
}
func (ec *executionContext) marshalNOwnedList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnedList(ctx context.Context, sel ast.SelectionSet, v *OwnedList) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._OwnedList(ctx, sel, v)
}
func (ec *executionContext) marshalNProfileIcon2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v ProfileIcon) graphql.Marshaler { func (ec *executionContext) marshalNProfileIcon2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v ProfileIcon) graphql.Marshaler {
return ec._ProfileIcon(ctx, sel, &v) return ec._ProfileIcon(ctx, sel, &v)
} }
@ -16829,17 +17275,6 @@ func (ec *executionContext) marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknott
return ec._ChecklistBadge(ctx, sel, v) return ec._ChecklistBadge(ctx, sel, v)
} }
func (ec *executionContext) marshalOOwnersList2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnersList(ctx context.Context, sel ast.SelectionSet, v OwnersList) graphql.Marshaler {
return ec._OwnersList(ctx, sel, &v)
}
func (ec *executionContext) marshalOOwnersList2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐOwnersList(ctx context.Context, sel ast.SelectionSet, v *OwnersList) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._OwnersList(ctx, sel, v)
}
func (ec *executionContext) unmarshalOProjectsFilter2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProjectsFilter(ctx context.Context, v interface{}) (ProjectsFilter, error) { func (ec *executionContext) unmarshalOProjectsFilter2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋinternalᚋgraphᚐProjectsFilter(ctx context.Context, v interface{}) (ProjectsFilter, error) {
return ec.unmarshalInputProjectsFilter(ctx, v) return ec.unmarshalInputProjectsFilter(ctx, v)
} }

48
internal/graph/helpers.go Normal file
View File

@ -0,0 +1,48 @@
package graph
import (
"context"
"database/sql"
"github.com/jordanknott/project-citadel/api/internal/db"
)
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
}
func GetMemberList(ctx context.Context, r db.Repository, user db.UserAccount) (*MemberList, error) {
projectMemberIDs, err := r.GetMemberProjectIDsForUserID(ctx, user.UserID)
if err != sql.ErrNoRows && err != nil {
return &MemberList{}, err
}
var projects []db.Project
for _, projectID := range projectMemberIDs {
project, err := r.GetProjectByID(ctx, projectID)
if err != nil {
return &MemberList{}, err
}
projects = append(projects, project)
}
teamMemberIDs, err := r.GetMemberTeamIDsForUserID(ctx, user.UserID)
if err != sql.ErrNoRows && err != nil {
return &MemberList{}, err
}
var teams []db.Team
for _, teamID := range teamMemberIDs {
team, err := r.GetTeamByID(ctx, teamID)
if err != nil {
return &MemberList{}, err
}
teams = append(teams, team)
}
return &MemberList{Teams: teams, Projects: projects}, nil
}

View File

@ -176,7 +176,13 @@ type Member struct {
FullName string `json:"fullName"` FullName string `json:"fullName"`
Username string `json:"username"` Username string `json:"username"`
ProfileIcon *ProfileIcon `json:"profileIcon"` ProfileIcon *ProfileIcon `json:"profileIcon"`
Owned *OwnersList `json:"owned"` Owned *OwnedList `json:"owned"`
Member *MemberList `json:"member"`
}
type MemberList struct {
Teams []db.Team `json:"teams"`
Projects []db.Project `json:"projects"`
} }
type NewProject struct { type NewProject struct {
@ -232,6 +238,11 @@ type NewUserAccount struct {
RoleCode string `json:"roleCode"` RoleCode string `json:"roleCode"`
} }
type OwnedList struct {
Teams []db.Team `json:"teams"`
Projects []db.Project `json:"projects"`
}
type OwnersList struct { type OwnersList struct {
Projects []uuid.UUID `json:"projects"` Projects []uuid.UUID `json:"projects"`
Teams []uuid.UUID `json:"teams"` Teams []uuid.UUID `json:"teams"`

View File

@ -46,7 +46,8 @@ type Member {
fullName: String! fullName: String!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnersList owned: OwnedList!
member: MemberList!
} }
type RefreshToken { type RefreshToken {
@ -61,6 +62,16 @@ type Role {
name: String! name: String!
} }
type OwnedList {
teams: [Team!]!
projects: [Project!]!
}
type MemberList {
teams: [Team!]!
projects: [Project!]!
}
type UserAccount { type UserAccount {
id: ID! id: ID!
email: String! email: String!
@ -70,6 +81,8 @@ type UserAccount {
role: Role! role: Role!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnedList!
member: MemberList!
} }
type Team { type Team {

View File

@ -113,16 +113,13 @@ func (r *mutationResolver) CreateProjectMember(ctx context.Context, input Create
return &CreateProjectMemberPayload{Ok: true, Member: &Member{ return &CreateProjectMemberPayload{Ok: true, Member: &Member{
ID: input.UserID, ID: input.UserID,
FullName: user.FullName, FullName: user.FullName,
Username: user.Username,
ProfileIcon: profileIcon, ProfileIcon: profileIcon,
Role: &db.Role{Code: role.Code, Name: role.Name}, Role: &db.Role{Code: role.Code, Name: role.Name},
}}, nil }}, nil
} }
func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) { func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) {
err := r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{UserID: input.UserID, ProjectID: input.ProjectID})
if err != nil {
return &DeleteProjectMemberPayload{Ok: false}, err
}
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID) user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
if err != nil { if err != nil {
return &DeleteProjectMemberPayload{Ok: false}, err return &DeleteProjectMemberPayload{Ok: false}, err
@ -136,6 +133,10 @@ func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input Delete
if err != nil { if err != nil {
return &DeleteProjectMemberPayload{Ok: false}, err 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{ return &DeleteProjectMemberPayload{Ok: true, Member: &Member{
ID: input.UserID, ID: input.UserID,
FullName: user.FullName, FullName: user.FullName,
@ -1156,20 +1157,15 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
log.WithError(err).Error("get user account by ID") log.WithError(err).Error("get user account by ID")
return members, err return members, err
} }
ownedProjects, err := r.Repository.GetOwnedTeamProjectsForUserID(ctx, db.GetOwnedTeamProjectsForUserIDParams{TeamID: obj.TeamID, Owner: user.UserID}) ownedList, err := GetOwnedList(ctx, r.Repository, user)
log.WithFields(log.Fields{"projects": ownedProjects}).Info("retrieved owned project list") if err != nil {
if err == sql.ErrNoRows {
ownedProjects = []uuid.UUID{}
} else if err != nil {
log.WithError(err).Error("get owned team projects for user id")
return members, err return members, err
} }
ownedTeams := []uuid.UUID{} memberList, err := GetMemberList(ctx, r.Repository, user)
var ownerList *OwnersList if err != nil {
if len(ownedTeams) != 0 || len(ownedProjects) != 0 { return members, err
log.Info("owned list is not empty")
ownerList = &OwnersList{Projects: ownedProjects, Teams: ownedTeams}
} }
var url *string var url *string
if user.ProfileAvatarUrl.Valid { if user.ProfileAvatarUrl.Valid {
url = &user.ProfileAvatarUrl.String url = &user.ProfileAvatarUrl.String
@ -1177,7 +1173,7 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
members = append(members, Member{ members = append(members, Member{
ID: obj.Owner, FullName: user.FullName, ProfileIcon: profileIcon, Username: user.Username, ID: obj.Owner, FullName: user.FullName, ProfileIcon: profileIcon, Username: user.Username,
Owned: ownerList, Role: &db.Role{Code: "owner", Name: "Owner"}, Owned: ownedList, Member: memberList, Role: &db.Role{Code: "owner", Name: "Owner"},
}) })
teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID) teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
if err != nil { if err != nil {
@ -1200,24 +1196,19 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
log.WithError(err).Error("get role for projet member by user ID") log.WithError(err).Error("get role for projet member by user ID")
return members, err return members, err
} }
ownedProjects, err := r.Repository.GetOwnedTeamProjectsForUserID(ctx, db.GetOwnedTeamProjectsForUserIDParams{TeamID: obj.TeamID, Owner: user.UserID})
log.WithFields(log.Fields{"projects": ownedProjects}).Info("retrieved owned project list") ownedList, err := GetOwnedList(ctx, r.Repository, user)
if err == sql.ErrNoRows { if err != nil {
ownedProjects = []uuid.UUID{}
} else if err != nil {
log.WithError(err).Error("get owned team projects for user id")
return members, err return members, err
} }
ownedTeams := []uuid.UUID{} memberList, err := GetMemberList(ctx, r.Repository, user)
var ownerList *OwnersList if err != nil {
if len(ownedTeams) != 0 || len(ownedProjects) != 0 { return members, err
log.Info("owned list is not empty")
ownerList = &OwnersList{Projects: ownedProjects, Teams: ownedTeams}
} }
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon, members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
Username: user.Username, Owned: ownerList, Role: &db.Role{Code: role.Code, Name: role.Name}, Username: user.Username, Owned: ownedList, Member: memberList, Role: &db.Role{Code: role.Code, Name: role.Name},
}) })
} }
return members, nil return members, nil
@ -1246,6 +1237,47 @@ func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *db.UserAccou
return profileIcon, nil return profileIcon, nil
} }
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
}
func (r *userAccountResolver) Member(ctx context.Context, obj *db.UserAccount) (*MemberList, error) {
projectMemberIDs, err := r.Repository.GetMemberProjectIDsForUserID(ctx, obj.UserID)
if err != sql.ErrNoRows && err != nil {
return &MemberList{}, err
}
var projects []db.Project
for _, projectID := range projectMemberIDs {
project, err := r.Repository.GetProjectByID(ctx, projectID)
if err != nil {
return &MemberList{}, err
}
projects = append(projects, project)
}
teamMemberIDs, err := r.Repository.GetMemberTeamIDsForUserID(ctx, obj.UserID)
if err != sql.ErrNoRows && err != nil {
return &MemberList{}, err
}
var teams []db.Team
for _, teamID := range teamMemberIDs {
team, err := r.Repository.GetTeamByID(ctx, teamID)
if err != nil {
return &MemberList{}, err
}
teams = append(teams, team)
}
return &MemberList{Teams: teams, Projects: projects}, err
}
// LabelColor returns LabelColorResolver implementation. // LabelColor returns LabelColorResolver implementation.
func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{r} } func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{r} }
@ -1274,7 +1306,9 @@ func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} } func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} }
// TaskChecklistItem returns TaskChecklistItemResolver implementation. // TaskChecklistItem returns TaskChecklistItemResolver implementation.
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver { return &taskChecklistItemResolver{r} } func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver {
return &taskChecklistItemResolver{r}
}
// TaskGroup returns TaskGroupResolver implementation. // TaskGroup returns TaskGroupResolver implementation.
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} } func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }

View File

@ -46,7 +46,8 @@ type Member {
fullName: String! fullName: String!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnersList owned: OwnedList!
member: MemberList!
} }
type RefreshToken { type RefreshToken {
@ -61,6 +62,16 @@ type Role {
name: String! name: String!
} }
type OwnedList {
teams: [Team!]!
projects: [Project!]!
}
type MemberList {
teams: [Team!]!
projects: [Project!]!
}
type UserAccount { type UserAccount {
id: ID! id: ID!
email: String! email: String!
@ -70,6 +81,8 @@ type UserAccount {
role: Role! role: Role!
username: String! username: String!
profileIcon: ProfileIcon! profileIcon: ProfileIcon!
owned: OwnedList!
member: MemberList!
} }
type Team { type Team {