feat: projects can be set to public

This commit is contained in:
Jordan Knott 2021-04-30 22:55:37 -05:00
parent 3e72271d9b
commit 04c12e4da9
38 changed files with 1849 additions and 1186 deletions

View File

@ -30,6 +30,7 @@
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
"react/require-default-props": "off",
"no-case-declarations": "off",
"no-plusplus": "off",
"react/prop-types": 0,

View File

@ -24,7 +24,7 @@
"@types/react-router-dom": "^5.1.3",
"@types/react-select": "^3.0.13",
"@types/react-timeago": "^4.1.1",
"@types/styled-components": "^5.0.0",
"@types/styled-components": "^5.1.0",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link": "^1.2.13",
@ -64,7 +64,7 @@
"react-timeago": "^4.4.0",
"react-toastify": "^6.0.8",
"rich-markdown-editor": "^10.6.5",
"styled-components": "^5.0.1",
"styled-components": "^5.1.0",
"typescript": "~3.7.2"
},
"proxy": "http://localhost:3333",
@ -93,10 +93,10 @@
]
},
"devDependencies": {
"@graphql-codegen/cli": "^1.13.2",
"@graphql-codegen/typescript": "^1.13.2",
"@graphql-codegen/typescript-operations": "^1.13.2",
"@graphql-codegen/typescript-react-apollo": "^1.13.2",
"@graphql-codegen/cli": "^1.21.4",
"@graphql-codegen/typescript": "^1.22.0",
"@graphql-codegen/typescript-operations": "^1.17.16",
"@graphql-codegen/typescript-react-apollo": "^2.2.4",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"eslint": "^6.8.0",

View File

@ -41,9 +41,7 @@ const AuthorizedRoutes = () => {
const { status } = x;
const response: ValidateTokenResponse = await x.json();
const { valid, userID } = response;
if (!valid) {
history.replace(`/login`);
} else {
if (valid) {
setUser(userID);
}
setLoading(false);

View File

@ -1,17 +1,28 @@
import React from 'react';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
import React, { useState } from 'react';
import ProjectSettings, { DeleteConfirm, DELETE_INFO, PublicConfirm } from 'shared/components/ProjectSettings';
import {
useDeleteProjectMutation,
GetProjectsDocument,
useToggleProjectVisibilityMutation,
} from 'shared/generated/graphql';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
type ProjectPopupProps = {
history: any;
name: string;
publicOn: string | null;
projectID: string;
};
const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID }) => {
const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID, publicOn: initialPublicOn }) => {
const { hidePopup, setTab } = usePopup();
const [publicOn, setPublicOn] = useState(initialPublicOn);
const [toggleProjectVisibility] = useToggleProjectVisibilityMutation({
onCompleted: data => {
setPublicOn(data.toggleProjectVisibility.project.publicOn);
},
});
const [deleteProject] = useDeleteProjectMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
@ -36,11 +47,28 @@ const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, projectID })
<>
<Popup title={null} tab={0}>
<ProjectSettings
publicOn={publicOn}
onToggleProjectVisible={visible => {
if (visible) {
setTab(2, { width: 300 });
} else {
toggleProjectVisibility({ variables: { projectID, isPublic: false } });
}
}}
onDeleteProject={() => {
setTab(1, { width: 300 });
}}
/>
</Popup>
<Popup title="Change to public?" tab={1}>
<PublicConfirm
onConfirm={() => {
if (projectID) {
toggleProjectVisibility({ variables: { projectID, isPublic: true } });
}
}}
/>
</Popup>
<Popup title={`Delete the "${name}" project?`} tab={1}>
<DeleteConfirm
description={DELETE_INFO.DELETE_PROJECTS.description}

View File

@ -1,5 +1,6 @@
import React from 'react';
import TopNavbar, { MenuItem } from 'shared/components/TopNavbar';
import LoggedOutNavbar from 'shared/components/TopNavbar/LoggedOut';
import { ProfileMenu } from 'shared/components/DropdownMenu';
import { useHistory } from 'react-router';
import { useCurrentUser } from 'App/context';
@ -11,6 +12,8 @@ import NotificationPopup, { NotificationItem } from 'shared/components/Notifcati
import theme from 'App/ThemeStyles';
import ProjectFinder from './ProjectFinder';
// TODO: Move to context based navbar?
type GlobalTopNavbarProps = {
nameOnly?: boolean;
projectID: string | null;
@ -30,7 +33,7 @@ type GlobalTopNavbarProps = {
onRemoveInvitedFromBoard?: (email: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
const LoggedInNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab,
onSetTab,
menuType,
@ -46,9 +49,9 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
onRemoveInvitedFromBoard,
onRemoveFromBoard,
}) => {
const { user, setUser } = useCurrentUser();
const { data } = useTopNavbarQuery();
const { showPopup, hidePopup } = usePopup();
const { setUser } = useCurrentUser();
const history = useHistory();
const onLogout = () => {
fetch('/auth/logout', {
@ -109,9 +112,6 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
}
};
if (!user) {
return null;
}
// TODO: readd permision check
// const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
const userIsTeamOrProjectAdmin = true;
@ -174,6 +174,8 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
}
};
const user = data ? data.me?.user : null;
return (
<>
<TopNavbar
@ -188,7 +190,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
);
}}
currentTab={currentTab}
user={data ? data.me.user : null}
user={user ?? null}
canEditProjectName={userIsTeamOrProjectAdmin}
canInviteUser={userIsTeamOrProjectAdmin}
onMemberProfile={onMemberProfile}
@ -215,4 +217,45 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
);
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
currentTab,
onSetTab,
menuType,
teamID,
onChangeProjectOwner,
onChangeRole,
name,
popupContent,
projectMembers,
projectInvitedMembers,
onInviteUser,
onSaveProjectName,
onRemoveInvitedFromBoard,
onRemoveFromBoard,
}) => {
const { user } = useCurrentUser();
if (user) {
return (
<LoggedInNavbar
currentTab={currentTab}
projectID={null}
onSetTab={onSetTab}
menuType={menuType}
teamID={teamID}
onChangeRole={onChangeRole}
onChangeProjectOwner={onChangeProjectOwner}
name={name}
popupContent={popupContent}
projectMembers={projectMembers}
projectInvitedMembers={projectInvitedMembers}
onInviteUser={onInviteUser}
onSaveProjectName={onSaveProjectName}
onRemoveInvitedFromBoard={onRemoveInvitedFromBoard}
onRemoveFromBoard={onRemoveFromBoard}
/>
);
}
return <LoggedOutNavbar name={name} menuType={menuType} />;
};
export default GlobalTopNavbar;

View File

@ -62,7 +62,7 @@ const Projects = () => {
}}
/>
<GlobalTopNavbar projectID={null} onSaveProjectName={NOOP} name={null} />
{!loading && data && (
{!loading && data && data.me && (
<Settings
profile={data.me.user}
onProfileAvatarChange={() => {

View File

@ -198,6 +198,7 @@ type ProjectBoardProps = {
};
export const BoardLoading = () => {
const { user } = useCurrentUser();
return (
<>
<ProjectBar>
@ -215,6 +216,7 @@ export const BoardLoading = () => {
<ProjectActionText>Filter</ProjectActionText>
</ProjectAction>
</ProjectActions>
{user && (
<ProjectActions>
<ProjectAction>
<Tags width={13} height={13} />
@ -229,6 +231,7 @@ export const BoardLoading = () => {
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
)}
</ProjectBar>
<EmptyBoard />
</>
@ -469,7 +472,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
}
return 'All Tasks';
};
if (data && user) {
if (data) {
labelsRef.current = data.findProject.labels;
membersRef.current = data.findProject.members;
const onQuickEditorOpen = ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => {
@ -570,6 +573,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
);
})}
</ProjectActions>
{user && (
<ProjectActions>
<ProjectAction
onClick={$labelsRef => {
@ -596,8 +600,10 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>
)}
</ProjectBar>
<SimpleLists
isPublic={user === null}
onTaskClick={task => {
history.push(`${match.url}/c/${task.id}`);
}}

View File

@ -424,7 +424,7 @@ const Details: React.FC<DetailsProps> = ({
updateTaskComment({ variables: { commentID, message } });
}}
editableComment={editableComment}
me={data.me.user}
me={data.me ? data.me.user : null}
onCommentShowActions={(commentID, $targetRef) => {
showPopup(
$targetRef,

View File

@ -163,10 +163,6 @@ const Project = () => {
}
}, [data]);
if (error) {
history.push('/projects');
}
if (data) {
labelsRef.current = data.findProject.labels;
@ -204,7 +200,14 @@ const Project = () => {
/>,
);
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
popupContent={
<ProjectPopup // eslint-disable-line
history={history}
publicOn={data.findProject.publicOn}
name={data.findProject.name}
projectID={projectID}
/>
}
menuType={[{ name: 'Board', link: location.pathname }]}
currentTab={0}
projectMembers={data.findProject.members}

View File

@ -58,12 +58,14 @@ type Props = {
onCardTitleChange?: (name: string) => void;
labelVariant?: CardLabelVariant;
toggleLabels?: boolean;
isPublic?: boolean;
toggleDirection?: 'shrink' | 'expand';
};
const Card = React.forwardRef(
(
{
isPublic = false,
wrapperProps,
onContextMenu,
taskID,
@ -120,9 +122,11 @@ const Card = React.forwardRef(
}
};
const onTaskContext = (e: React.MouseEvent) => {
if (!isPublic) {
e.preventDefault();
e.stopPropagation();
onOpenComposer();
}
};
const onOperationClick = (e: React.MouseEvent<HTMLOrSVGElement>) => {
e.preventDefault();
@ -145,7 +149,7 @@ const Card = React.forwardRef(
{...wrapperProps}
>
<ListCardInnerContainer ref={$innerCardRef}>
{isActive && !editable && (
{!isPublic && isActive && !editable && (
<ListCardOperation
onClick={e => {
e.stopPropagation();

View File

@ -72,6 +72,9 @@ export const HeaderName = styled(TextareaAutosize)`
box-shadow: none;
font-weight: 600;
margin: -4px 0;
&:disabled {
opacity: 1;
}
letter-spacing: normal;
word-spacing: normal;

View File

@ -24,6 +24,7 @@ type Props = {
onOpenComposer: (id: string) => void;
wrapperProps?: any;
headerProps?: any;
isPublic: boolean;
index?: number;
onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void;
};
@ -37,6 +38,7 @@ const List = React.forwardRef(
isComposerOpen,
onOpenComposer,
children,
isPublic,
wrapperProps,
headerProps,
onExtraMenuOpen,
@ -86,39 +88,37 @@ const List = React.forwardRef(
<Container ref={$wrapperRef} {...wrapperProps}>
<Wrapper>
<Header {...headerProps} isEditing={isEditingTitle}>
<HeaderEditTarget onClick={onClick} isHidden={isEditingTitle} />
{!isPublic && <HeaderEditTarget onClick={onClick} isHidden={isEditingTitle} />}
<HeaderName
ref={$listNameRef}
disabled={isPublic}
onBlur={onBlur}
onChange={onChange}
onKeyDown={onKeyDown}
spellCheck={false}
value={listName}
/>
{!isPublic && (
<ListExtraMenuButtonWrapper ref={$extraActionsRef} onClick={handleExtraMenuOpen}>
<Ellipsis size={16} color="#c2c6dc" />
</ListExtraMenuButtonWrapper>
)}
</Header>
{children && children}
{!isPublic && (
<AddCardContainer hidden={isComposerOpen}>
<AddCardButton onClick={() => onOpenComposer(id)}>
<Plus width={12} height={12} />
<AddCardButtonText>Add another card</AddCardButtonText>
</AddCardButton>
</AddCardContainer>
)}
</Wrapper>
</Container>
);
},
);
List.defaultProps = {
children: null,
isComposerOpen: false,
wrapperProps: {},
headerProps: {},
};
List.displayName = 'List';
export default List;

View File

@ -151,6 +151,7 @@ interface SimpleProps {
onCardMemberClick: OnCardMemberClick;
onCardLabelClick: () => void;
cardLabelVariant: CardLabelVariant;
isPublic?: boolean;
taskStatusFilter?: TaskStatusFilter;
taskMetaFilters?: TaskMetaFilters;
taskSorting?: TaskSorting;
@ -188,6 +189,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onExtraMenuOpen,
onCardMemberClick,
taskStatusFilter = initTaskStatusFilter,
isPublic = false,
taskMetaFilters = initTaskMetaFilters,
taskSorting = initTaskSorting,
}) => {
@ -300,6 +302,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
isPublic={isPublic}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
@ -328,6 +331,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
<Card
toggleDirection={toggleDirection}
toggleLabels={toggleLabels}
isPublic={isPublic}
labelVariant={cardLabelVariant}
wrapperProps={{
...taskProvided.draggableProps,
@ -396,11 +400,13 @@ const SimpleLists: React.FC<SimpleProps> = ({
)}
</Droppable>
</DragDropContext>
{!isPublic && (
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
)}
</BoardWrapper>
</BoardContainer>
);

View File

@ -38,12 +38,17 @@ export const ListSeparator = styled.hr`
`;
type Props = {
publicOn: null | string;
onDeleteProject: () => void;
onToggleProjectVisible: (visible: boolean) => void;
};
const ProjectSettings: React.FC<Props> = ({ onDeleteProject }) => {
const ProjectSettings: React.FC<Props> = ({ publicOn, onDeleteProject, onToggleProjectVisible }) => {
return (
<>
<ListActionsWrapper>
<ListActionItemWrapper onClick={() => onToggleProjectVisible(publicOn === null)}>
<ListActionItem>{`Make ${publicOn === null ? 'public' : 'private'}`}</ListActionItem>
</ListActionItemWrapper>
<ListActionItemWrapper onClick={() => onDeleteProject()}>
<ListActionItem>Delete Project</ListActionItem>
</ListActionItemWrapper>
@ -127,5 +132,18 @@ const DeleteConfirm: React.FC<DeleteConfirmProps> = ({ description, deletedItems
);
};
export { DeleteConfirm };
type PublicConfirmProps = {
onConfirm: () => void;
};
const PublicConfirm: React.FC<PublicConfirmProps> = ({ onConfirm }) => {
return (
<ConfirmWrapper>
<ConfirmDescription>Public projects can be accessed by anyone with a link to the project.</ConfirmDescription>
<ConfirmDeleteButton onClick={() => onConfirm()}>Make public</ConfirmDeleteButton>
</ConfirmWrapper>
);
};
export { DeleteConfirm, PublicConfirm };
export default ProjectSettings;

View File

@ -69,7 +69,7 @@ const CommentCreator: React.FC<CommentCreatorProps> = ({
)}
<CommentEditorContainer>
<CommentTextArea
showCommentActions={showCommentActions}
$showCommentActions={showCommentActions}
placeholder="Write a comment..."
ref={$comment}
disabled={disabled}

View File

@ -80,30 +80,33 @@ import {
ActivityItemHeaderTitleName,
ActivityItemComment,
} from './Styles';
import { useCurrentUser } from 'App/context';
type TaskDetailsProps = {};
const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
const { user } = useCurrentUser();
return (
<Container>
<LeftSidebar>
<LeftSidebarContent>
<LeftSidebarSection>
<SidebarTitle>TASK GROUP</SidebarTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
<DueDateTitle>DUE DATE</DueDateTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
</LeftSidebarSection>
<AssignedUsersSection>
<DueDateTitle>MEMBERS</DueDateTitle>
<SidebarButton loading>
<SidebarButton $loading>
<SidebarSkeleton />
</SidebarButton>
</AssignedUsersSection>
{user && (
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton disabled icon={<Tags width={12} height={12} />}>
@ -114,6 +117,7 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
</ActionButton>
<ActionButton disabled>Cover</ActionButton>
</ExtraActionsSection>
)}
</LeftSidebarContent>
</LeftSidebar>
<ContentContainer>
@ -125,6 +129,7 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
<span>Mark complete</span>
</MarkCompleteButton>
</HeaderLeft>
{user && (
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
@ -139,9 +144,10 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
)}
</HeaderInnerContainer>
<TaskDetailsTitleWrapper loading>
<TaskDetailsTitle value="" disabled loading />
<TaskDetailsTitleWrapper $loading>
<TaskDetailsTitle value="" disabled $loading />
</TaskDetailsTitleWrapper>
</HeaderContainer>
<InnerContentContainer>
@ -151,9 +157,11 @@ const TaskDetailsLoading: React.FC<TaskDetailsProps> = () => {
</TabBarSection>
<ActivitySection />
</InnerContentContainer>
{user && (
<CommentContainer>
<CommentCreator disabled onCreateComment={() => null} onMemberProfile={() => null} />
</CommentContainer>
)}
</ContentContainer>
</Container>
);

View File

@ -108,7 +108,7 @@ export const skeletonKeyframes = keyframes`
}
`;
export const SidebarButton = styled.div<{ loading?: boolean }>`
export const SidebarButton = styled.div<{ $loading?: boolean }>`
font-size: 14px;
color: ${props => props.theme.colors.text.primary};
min-height: 32px;
@ -116,7 +116,7 @@ export const SidebarButton = styled.div<{ loading?: boolean }>`
border-radius: 6px;
${props =>
props.loading
props.$loading
? css`
background: ${props.theme.colors.bg.primary};
`
@ -178,15 +178,15 @@ export const HeaderLeft = styled.div`
justify-content: flex-start;
`;
export const TaskDetailsTitleWrapper = styled.div<{ loading?: boolean }>`
export const TaskDetailsTitleWrapper = styled.div<{ $loading?: boolean }>`
width: 100%;
margin: 8px 0 4px 0;
display: flex;
border-radius: 6px;
${props => props.loading && `background: ${props.theme.colors.bg.primary};`}
${props => props.$loading && `background: ${props.theme.colors.bg.primary};`}
`;
export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
export const TaskDetailsTitle = styled(TextareaAutosize)<{ $loading?: boolean }>`
padding: 9px 8px 7px 8px;
border-color: transparent;
border-radius: 6px;
@ -198,8 +198,11 @@ export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
font-weight: 700;
background: none;
&:disabled {
opacity: 1;
}
${props =>
props.loading
props.$loading
? css`
background-image: linear-gradient(90deg, ${defaultBaseColor}, ${defaultHighlightColor}, ${defaultBaseColor});
background-size: 200px 100%;
@ -207,7 +210,7 @@ export const TaskDetailsTitle = styled(TextareaAutosize)<{ loading?: boolean }>`
animation: ${skeletonKeyframes} 1.2s ease-in-out infinite;
`
: css`
&:hover {
&:not(:disabled):hover {
border-color: #414561;
border-width: 1px;
border-style: solid;
@ -534,7 +537,7 @@ export const CommentProfile = styled(TaskAssignee)`
align-items: normal;
`;
export const CommentTextArea = styled(TextareaAutosize)<{ showCommentActions: boolean }>`
export const CommentTextArea = styled(TextareaAutosize)<{ $showCommentActions: boolean }>`
width: 100%;
line-height: 28px;
padding: 4px 6px;
@ -546,7 +549,7 @@ export const CommentTextArea = styled(TextareaAutosize)<{ showCommentActions: bo
min-height: 36px;
max-height: 36px;
${props =>
props.showCommentActions
props.$showCommentActions
? css`
min-height: 80px;
max-height: none;

View File

@ -83,6 +83,7 @@ import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
import onDragEnd from './onDragEnd';
import { plugin as em } from './remark';
import ActivityMessage from './ActivityMessage';
import { useCurrentUser } from 'App/context';
const parseEmojis = (value: string) => {
const emojisArray = toArray(value);
@ -277,6 +278,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onToggleChecklistItem,
onMemberProfile,
}) => {
const { user } = useCurrentUser();
const [taskName, setTaskName] = useState(task.name);
const [editTaskDescription, setEditTaskDescription] = useState(() => {
if (task.description) {
@ -338,7 +340,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<SidebarButton
ref={$dueDateBtn}
onClick={() => {
if (user) {
onOpenDueDatePopop(task, $dueDateBtn);
}
}}
>
{task.dueDate ? (
@ -360,14 +364,18 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
member={m}
size={32}
onMemberProfile={$target => {
if (user) {
onMemberProfile($target, m.id);
}
}}
/>
))}
<AssignUserIcon
ref={$addMemberBtn}
onClick={() => {
if (user) {
onOpenAddMemberPopup(task, $addMemberBtn);
}
}}
>
<Plus width={16} height={16} />
@ -377,7 +385,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<AssignUsersButton
ref={$noMemberBtn}
onClick={() => {
if (user) {
onOpenAddMemberPopup(task, $noMemberBtn);
}
}}
>
<AssignUserIcon>
@ -387,6 +397,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</AssignUsersButton>
)}
</AssignedUsersSection>
{user && (
<ExtraActionsSection>
<DueDateTitle>ACTIONS</DueDateTitle>
<ActionButton
@ -407,6 +418,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</ActionButton>
<ActionButton>Cover</ActionButton>
</ExtraActionsSection>
)}
</LeftSidebarContent>
</LeftSidebar>
<ContentContainer>
@ -414,15 +426,19 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<HeaderInnerContainer>
<HeaderLeft>
<MarkCompleteButton
disabled={user === null}
invert={task.complete ?? false}
onClick={() => {
if (user) {
onToggleTaskComplete(task);
}
}}
>
<Checkmark width={8} height={8} />
<span>{task.complete ? 'Completed' : 'Mark complete'}</span>
</MarkCompleteButton>
</HeaderLeft>
{user && (
<HeaderRight>
<HeaderActionIcon>
<Paperclip width={16} height={16} />
@ -437,11 +453,13 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<Trash width={16} height={16} />
</HeaderActionIcon>
</HeaderRight>
)}
</HeaderInnerContainer>
<TaskDetailsTitleWrapper>
<TaskDetailsTitle
value={taskName}
ref={$detailsTitle}
disabled={user === null}
onKeyDown={e => {
if (e.keyCode === 13) {
e.preventDefault();
@ -496,7 +514,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<Editor
defaultValue={task.description ?? ''}
theme={dark}
readOnly={!editTaskDescription}
readOnly={user === null || !editTaskDescription}
autoFocus
onChange={value => {
setSaveTimeout(() => {
@ -612,15 +630,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
)}
</ActivitySection>
</InnerContentContainer>
<CommentContainer>
{me && (
<CommentContainer>
<CommentCreator
me={me}
onCreateComment={message => onCreateComment(task, message)}
onMemberProfile={onMemberProfile}
/>
)}
</CommentContainer>
)}
</ContentContainer>
</Container>
);

View File

@ -0,0 +1,63 @@
import React, { useRef, useState, useEffect } from 'react';
import { Home, Star, Bell, AngleDown, BarChart, CheckCircle, ListUnordered } from 'shared/icons';
import { RoleCode } from 'shared/generated/graphql';
import * as S from './Styles';
import { Link } from 'react-router-dom';
export type MenuItem = {
name: string;
link: string;
};
type NavBarProps = {
menuType?: Array<MenuItem> | null;
name: string | null;
};
const NavBar: React.FC<NavBarProps> = ({ menuType, name }) => {
return (
<S.NavbarWrapper>
<S.NavbarHeader>
<S.ProjectActions>
<S.ProjectSwitch>
<S.ProjectSwitchInner>
<S.TaskcafeLogo innerColor="#9f46e4" outerColor="#000" width={32} height={32} />
</S.ProjectSwitchInner>
</S.ProjectSwitch>
<S.ProjectInfo>
<S.ProjectMeta>{name && <S.ProjectName>{name}</S.ProjectName>}</S.ProjectMeta>
{name && (
<S.ProjectTabs>
{menuType &&
menuType.map((menu, idx) => {
return (
<S.ProjectTab
key={menu.name}
to={menu.link}
exact
onClick={() => {
// TODO
}}
>
{menu.name}
</S.ProjectTab>
);
})}
</S.ProjectTabs>
)}
</S.ProjectInfo>
</S.ProjectActions>
<S.LogoContainer to="/">
<S.TaskcafeTitle>Taskcafé</S.TaskcafeTitle>
</S.LogoContainer>
<S.GlobalActions>
<Link to="/login">
<S.SignIn>Sign In</S.SignIn>
</Link>
</S.GlobalActions>
</S.NavbarHeader>
</S.NavbarWrapper>
);
};
export default NavBar;

View File

@ -297,6 +297,16 @@ export const ProjectFinder = styled(Button)`
padding: 6px 12px;
`;
export const SignUp = styled(Button)`
margin-right: 8px;
padding: 6px 12px;
`;
export const SignIn = styled(Button)`
margin-right: 20px;
padding: 6px 12px;
`;
export const NavSeparator = styled.div`
width: 1px;
background: ${props => props.theme.colors.border};

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ const FIND_PROJECT_QUERY = gql`
query findProject($projectID: UUID!) {
findProject(input: { projectID: $projectID }) {
name
publicOn
team {
id
}

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag';
export const DELETE_PROJECT_MUTATION = gql`
mutation toggleProjectVisibility($projectID: UUID!, $isPublic: Boolean!) {
toggleProjectVisibility(input: { projectID: $projectID, isPublic: $isPublic }) {
project {
id
publicOn
}
}
}
`;
export default DELETE_PROJECT_MUTATION;

View File

@ -39,11 +39,6 @@
ts-invariant "^0.4.4"
tslib "^1.10.0"
"@ardatan/aggregate-error@0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.1.tgz#1403ac5de10d8ca689fc1f65844c27179ae1d44f"
integrity sha512-UQ9BequOTIavs0pTHLMwQwKQF8tTV1oezY/H2O9chA+JNPFZSua55xpU5dPSjAU9/jLJ1VwU+HJuTVN8u7S6Fg==
"@ardatan/aggregate-error@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609"
@ -1349,13 +1344,13 @@
ts-node "^9"
tslib "^2"
"@graphql-codegen/cli@^1.13.2":
version "1.19.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.19.4.tgz#16e5caaa46ee159cbb01e2a2699203f8f25faac2"
integrity sha512-PN1xgaXZaTTEPhBfQkIRK82RzrzY8Pr+tfCqOtibeJMed0mtIJ5gSQfollRbtZ0/t7ZEmYCxojqQiVSbKzXDMw==
"@graphql-codegen/cli@^1.21.4":
version "1.21.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.21.4.tgz#41ce6abc6b33e369a3ee795621373b8ffa1aadeb"
integrity sha512-fCrPn7DeGnCU6d9xjo04VSzIGioVLj1BAjuZpgRJNNjyzTmv+qZJJVTWfD55J17XxlhFsfKJBprtdmRxZ3V2hw==
dependencies:
"@graphql-codegen/core" "1.17.9"
"@graphql-codegen/plugin-helpers" "^1.18.2"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-tools/apollo-engine-loader" "^6"
"@graphql-tools/code-file-loader" "^6"
"@graphql-tools/git-loader" "^6"
@ -1365,16 +1360,15 @@
"@graphql-tools/load" "^6"
"@graphql-tools/prisma-loader" "^6"
"@graphql-tools/url-loader" "^6"
"@graphql-tools/utils" "^6"
"@graphql-tools/utils" "^7.0.0"
ansi-escapes "^4.3.1"
camel-case "^4.1.1"
chalk "^4.1.0"
change-case-all "1.0.14"
chokidar "^3.4.3"
common-tags "^1.8.0"
constant-case "^3.0.3"
cosmiconfig "^7.0.0"
debounce "^1.2.0"
dependency-graph "^0.9.0"
dependency-graph "^0.11.0"
detect-indent "^6.0.0"
glob "^7.1.6"
graphql-config "^3.2.0"
@ -1386,17 +1380,14 @@
listr "^0.14.3"
listr-update-renderer "^0.5.0"
log-symbols "^4.0.0"
lower-case "^2.0.1"
minimatch "^3.0.4"
mkdirp "^1.0.4"
pascal-case "^3.1.1"
request "^2.88.2"
string-env-interpolation "^1.0.1"
ts-log "^2.2.3"
tslib "~2.0.1"
upper-case "^2.0.1"
tslib "~2.2.0"
valid-url "^1.0.9"
wrap-ansi "^7.0.0"
yaml "^1.10.0"
yargs "^16.1.1"
"@graphql-codegen/core@1.17.9":
@ -1409,23 +1400,6 @@
"@graphql-tools/utils" "^6"
tslib "~2.0.1"
"@graphql-codegen/plugin-helpers@1.17.7":
version "1.17.7"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.17.7.tgz#5903105fda9470aafefe9da29e3a6fb3a52b8376"
integrity sha512-LsXS0s/ZOACZXa3W29ekcaQLzP8TsYzow6nIjW6rtkWX5T0EDooBQbDn1cdLdlpenqbUU+vtONwR6Qqc6hrq2Q==
dependencies:
"@graphql-tools/utils" "^6.0.0"
camel-case "4.1.1"
common-tags "1.8.0"
constant-case "3.0.3"
import-from "3.0.0"
lodash "~4.17.15"
lower-case "2.0.1"
param-case "3.0.3"
pascal-case "3.1.1"
tslib "~2.0.0"
upper-case "2.0.1"
"@graphql-codegen/plugin-helpers@^1.18.2":
version "1.18.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.2.tgz#57011076cb8b8f5d04d37d226a5eda300c01be94"
@ -1443,69 +1417,64 @@
tslib "~2.0.1"
upper-case "2.0.1"
"@graphql-codegen/typescript-operations@^1.13.2":
version "1.17.12"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-1.17.12.tgz#25e0f0ede1bcd165196f0c161286d745395c7768"
integrity sha512-UXe/O3kNAlLptURuripAhK8n/Gf6FthL7xr6sG4vpuLVwrWjXu/KlS7+TP+UvlI9OH2LICm0KHqA0xoFCjNbMg==
"@graphql-codegen/plugin-helpers@^1.18.5":
version "1.18.5"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.5.tgz#e1d875cfb6a2f7bf4b4318135f7fee6e1200f3b0"
integrity sha512-xY8dWdU4+mm+253esLYcKQIgWZEgI3spKPmMRQ+oAxlrCn9oIcdhhiMqNxa9rHS20xpZtlAjozxHulrqjFLuyA==
dependencies:
"@graphql-codegen/plugin-helpers" "^1.18.2"
"@graphql-codegen/typescript" "^1.18.1"
"@graphql-codegen/visitor-plugin-common" "^1.17.20"
auto-bind "~4.0.0"
tslib "~2.0.1"
"@graphql-tools/utils" "^7.0.0"
common-tags "1.8.0"
import-from "3.0.0"
lodash "~4.17.20"
tslib "~2.2.0"
"@graphql-codegen/typescript-react-apollo@^1.13.2":
version "1.17.7"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-1.17.7.tgz#b1d1ad1150c07097566f46c208ca54d42a8ad4f5"
integrity sha512-Yoc7XwruETJ1Aoi5UBLF+AVsyReGPxI9jEpsNWv/Xm5zzGGk8iVwKIwos9hBjzkDO0sQooZZa0UXBKXNQa7j0w==
"@graphql-codegen/typescript-operations@^1.17.16":
version "1.17.16"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-1.17.16.tgz#75eb389f268b2dbd2e46b235bcb957be561c31cb"
integrity sha512-DoWIhg/c2XS4IpgRLRXRBJDwmKoIl6KuzL2iO9GElX0rdjYguwbqx6iDV1pgktTTajMy8pXP56oZhmEcZLRD2Q==
dependencies:
"@graphql-codegen/plugin-helpers" "1.17.7"
"@graphql-codegen/visitor-plugin-common" "1.17.7"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-codegen/typescript" "^1.22.0"
"@graphql-codegen/visitor-plugin-common" "^1.20.0"
auto-bind "~4.0.0"
camel-case "4.1.1"
pascal-case "3.1.1"
tslib "~2.0.0"
tslib "~2.2.0"
"@graphql-codegen/typescript@^1.13.2", "@graphql-codegen/typescript@^1.18.1":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.19.0.tgz#05b1b4502b91dee53ec4f0ae39e9537eff004c5c"
integrity sha512-ThpMdtb6LmEKzLNxlotpk33eIMfX1i1aAlYDaqctIhFjZ2Rbvkgdb8q/5/my7yeH33BLnuqHDKrdkDsAAvCHcw==
"@graphql-codegen/typescript-react-apollo@^2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-2.2.4.tgz#149b8e4f2b684295a2e6be8b9e88285a0f8be0f6"
integrity sha512-2db+KrujuBjQ8GraamEPGEp74Bxcw6Vd2AubhqenoTmHjK/Jw3HLpO8ndetcoND08e3EZCb/CNyKkLcxwYD0eg==
dependencies:
"@graphql-codegen/plugin-helpers" "^1.18.2"
"@graphql-codegen/visitor-plugin-common" "^1.17.21"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-codegen/visitor-plugin-common" "^1.20.0"
auto-bind "~4.0.0"
tslib "~2.0.1"
change-case-all "1.0.14"
tslib "~2.2.0"
"@graphql-codegen/visitor-plugin-common@1.17.7":
version "1.17.7"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.7.tgz#3158ca4fc7d45a0f5a6ad0706061015eae481d9e"
integrity sha512-z5WvYqgCgPAAuMJMOE0e0nEicdaQRm59/vP+yYihKQmwrASzPlqa1BUiGDnfrPcCooB9fEUHB+cQb3gt9GRfEg==
"@graphql-codegen/typescript@^1.22.0":
version "1.22.0"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.22.0.tgz#d05be3a971e5d75a076a43e123b6330f4366a6ab"
integrity sha512-YzN/3MBYHrP110m8JgUWQIHt7Ivi3JXiq0RT5XNx/F9mVOSbZz6Ezbaji8YJA3y04Gl2f6ZgtdGazWANUvcOcg==
dependencies:
"@graphql-codegen/plugin-helpers" "1.17.7"
"@graphql-tools/relay-operation-optimizer" "6.0.15"
array.prototype.flatmap "1.2.3"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-codegen/visitor-plugin-common" "^1.20.0"
auto-bind "~4.0.0"
dependency-graph "0.9.0"
graphql-tag "2.11.0"
parse-filepath "1.0.2"
pascal-case "3.1.1"
tslib "~2.0.0"
tslib "~2.2.0"
"@graphql-codegen/visitor-plugin-common@^1.17.20", "@graphql-codegen/visitor-plugin-common@^1.17.21":
version "1.17.21"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.21.tgz#e47a5623392bcdb89d5af2484798a2fd9a59df8c"
integrity sha512-rhdJdj+4DAMgMSBlgkvfOMw65L46sAtYb7G/n9ucc9uJGUbiIcgvS/wpoQ/gxz2eUaNfcoFmjVLm+dkTM/XwqA==
"@graphql-codegen/visitor-plugin-common@^1.20.0":
version "1.20.0"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.20.0.tgz#38d829eab7370c79aa5229190788f94adcae8f76"
integrity sha512-AYrpy8NA3DpvhDLqYGerQRv44S+YAMPKtwT8x9GNVjzP0gVfmqi3gG1bDWbP5sm6kOZKvDC0kTxGePuBSZerxw==
dependencies:
"@graphql-codegen/plugin-helpers" "^1.18.2"
"@graphql-codegen/plugin-helpers" "^1.18.5"
"@graphql-tools/optimize" "^1.0.1"
"@graphql-tools/relay-operation-optimizer" "^6"
array.prototype.flatmap "^1.2.4"
auto-bind "~4.0.0"
dependency-graph "^0.9.0"
change-case-all "1.0.14"
dependency-graph "^0.11.0"
graphql-tag "^2.11.0"
parse-filepath "^1.0.2"
pascal-case "^3.1.1"
tslib "~2.0.1"
tslib "~2.2.0"
"@graphql-tools/apollo-engine-loader@^6":
version "6.2.5"
@ -1665,14 +1634,6 @@
tslib "~2.0.1"
yaml-ast-parser "^0.0.43"
"@graphql-tools/relay-operation-optimizer@6.0.15":
version "6.0.15"
resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.15.tgz#f991499c54945cb8fa2396bb5292252fbda0a773"
integrity sha512-Y4h2kclKh5HvyvmoxeZiDhqdhMKfLKamOYx6UVpFsbeKb6Tt9RCNPVhpa+YToXxUXl0PvjhxZWeQ4lZY0GE0ug==
dependencies:
"@graphql-tools/utils" "6.0.15"
relay-compiler "10.0.0"
"@graphql-tools/relay-operation-optimizer@^6":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.3.0.tgz#f8c7f6c8aa4a9cf50ab151fbc5db4f4282a79532"
@ -1713,14 +1674,6 @@
valid-url "1.0.9"
ws "7.4.1"
"@graphql-tools/utils@6.0.15":
version "6.0.15"
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-6.0.15.tgz#6d54d383285bea3c22797531933b62a408e78e49"
integrity sha512-VG5cMLPgh9RDLGHamGpXVnBrNw7bZGT46LrxK7IIqDZI9H0GPsRCo8+p+CfDkw0IlDiEECb624WVCpm9IYNecA==
dependencies:
"@ardatan/aggregate-error" "0.0.1"
camel-case "4.1.1"
"@graphql-tools/utils@^6", "@graphql-tools/utils@^6.0.0":
version "6.2.4"
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-6.2.4.tgz#38a2314d2e5e229ad4f78cca44e1199e18d55856"
@ -2413,10 +2366,10 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/styled-components@^5.0.0":
version "5.1.7"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.7.tgz#3cd10b088c1cb1acde2e4b166b3e8275a3083710"
integrity sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA==
"@types/styled-components@^5.1.0":
version "5.1.9"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.9.tgz#00d3d84b501420521c4db727e3c195459f87a6cf"
integrity sha512-kbEG6YlwK8rucITpKEr6pA4Ho9KSQHUUOzZ9lY3va1mtcjvS3D0wDciFyHEiNHKLL/npZCKDQJqm0x44sPO9oA==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
@ -3076,15 +3029,6 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.1"
array.prototype.flatmap@1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443"
integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
function-bind "^1.1.1"
array.prototype.flatmap@^1.2.3, array.prototype.flatmap@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9"
@ -3881,7 +3825,7 @@ camel-case@4.1.1:
pascal-case "^3.1.1"
tslib "^1.10.0"
camel-case@4.1.2, camel-case@^4.1.1:
camel-case@4.1.2, camel-case@^4.1.1, camel-case@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
@ -3919,6 +3863,15 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz#6fcd098c139d003b9bd484cbb9ca26cb89907f9a"
integrity sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==
capital-case@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669"
integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
upper-case-first "^2.0.2"
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@ -3964,6 +3917,40 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
change-case-all@1.0.14:
version "1.0.14"
resolved "https://registry.yarnpkg.com/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1"
integrity sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==
dependencies:
change-case "^4.1.2"
is-lower-case "^2.0.2"
is-upper-case "^2.0.2"
lower-case "^2.0.2"
lower-case-first "^2.0.2"
sponge-case "^1.0.1"
swap-case "^2.0.2"
title-case "^3.0.3"
upper-case "^2.0.2"
upper-case-first "^2.0.2"
change-case@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12"
integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==
dependencies:
camel-case "^4.1.2"
capital-case "^1.0.4"
constant-case "^3.0.4"
dot-case "^3.0.4"
header-case "^2.0.4"
no-case "^3.0.4"
param-case "^3.0.4"
pascal-case "^3.1.2"
path-case "^3.0.4"
sentence-case "^3.0.4"
snake-case "^3.0.4"
tslib "^2.0.3"
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
@ -4369,7 +4356,7 @@ constant-case@3.0.3:
tslib "^1.10.0"
upper-case "^2.0.1"
constant-case@^3.0.3:
constant-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==
@ -4459,7 +4446,7 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.8.1.tgz#23f84048f366fdfcf52d3fd1c68fec349177d119"
integrity sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==
core-js@^2.4.0, core-js@^2.4.1:
core-js@^2.4.0:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
@ -5042,10 +5029,10 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
dependency-graph@0.9.0, dependency-graph@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318"
integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w==
dependency-graph@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27"
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
des.js@^1.0.0:
version "1.0.1"
@ -5407,13 +5394,6 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -6117,20 +6097,6 @@ fbjs-css-vars@^1.0.0:
resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
fbjs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a"
integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==
dependencies:
core-js "^2.4.1"
fbjs-css-vars "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
fbjs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165"
@ -6711,7 +6677,7 @@ graphql-request@^3.3.0:
extract-files "^9.0.0"
form-data "^3.0.0"
graphql-tag@2.11.0, graphql-tag@^2.10.3, graphql-tag@^2.11.0:
graphql-tag@^2.10.3, graphql-tag@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd"
integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==
@ -6876,6 +6842,14 @@ he@^1.1.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
header-case@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063"
integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==
dependencies:
capital-case "^1.0.4"
tslib "^2.0.3"
hex-color-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
@ -7128,13 +7102,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-utils@^4.0.0, icss-utils@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
@ -7591,6 +7558,13 @@ is-hexadecimal@^1.0.0:
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a"
integrity sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==
dependencies:
tslib "^2.0.3"
is-negative-zero@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
@ -7695,7 +7669,7 @@ is-root@2.1.0:
resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c"
integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==
is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@ -7731,6 +7705,13 @@ is-unc-path@^1.0.0:
dependencies:
unc-path-regex "^0.1.2"
is-upper-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-2.0.2.tgz#f1105ced1fe4de906a5f39553e7d3803fd804649"
integrity sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==
dependencies:
tslib "^2.0.3"
is-whitespace-character@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7"
@ -7790,14 +7771,6 @@ isobject@^4.0.0:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isomorphic-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
@ -8841,7 +8814,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.15, lodash@~4.17.20:
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.20:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@ -8881,6 +8854,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lower-case-first@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-2.0.2.tgz#64c2324a2250bf7c37c5901e76a5b5309301160b"
integrity sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==
dependencies:
tslib "^2.0.3"
lower-case@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7"
@ -8888,7 +8868,7 @@ lower-case@2.0.1:
dependencies:
tslib "^1.10.0"
lower-case@^2.0.1, lower-case@^2.0.2:
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
@ -9397,14 +9377,6 @@ node-fetch@2.6.1, node-fetch@^2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@ -9919,7 +9891,7 @@ param-case@3.0.3:
dot-case "^3.0.3"
tslib "^1.10.0"
param-case@^3.0.3:
param-case@^3.0.3, param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
@ -9969,7 +9941,7 @@ parse-entities@^2.0.0:
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parse-filepath@1.0.2, parse-filepath@^1.0.2:
parse-filepath@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=
@ -10044,6 +10016,14 @@ path-browserify@0.0.1:
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
path-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"
integrity sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
path-dirname@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
@ -11844,28 +11824,6 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
relay-compiler@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-10.0.0.tgz#04d50d8ec53e3f683bc379b756cf0542a76105af"
integrity sha512-EVBMcMCiP+waOPR2930cNCCsac1sNhfQayzS+bOEMz2Lls5Bx7grhaadkBZLTEdCHQ1kf7lrsmcMDqj9mxABFw==
dependencies:
"@babel/core" "^7.0.0"
"@babel/generator" "^7.5.0"
"@babel/parser" "^7.0.0"
"@babel/runtime" "^7.0.0"
"@babel/traverse" "^7.0.0"
"@babel/types" "^7.0.0"
babel-preset-fbjs "^3.3.0"
chalk "^4.0.0"
fb-watchman "^2.0.0"
fbjs "^1.0.0"
glob "^7.1.1"
immutable "~3.7.6"
nullthrows "^1.1.1"
relay-runtime "10.0.0"
signedsource "^1.0.0"
yargs "^15.3.1"
relay-compiler@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-10.1.0.tgz#fb4672cdbe9b54869a3a79759edd8c2d91609cbe"
@ -11888,14 +11846,6 @@ relay-compiler@10.1.0:
signedsource "^1.0.0"
yargs "^15.3.1"
relay-runtime@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-10.0.0.tgz#cfceb0f8453b39a385d63093f2dbf1702ddc02b3"
integrity sha512-QEpFwEjvGgWgQ0MPJyrZKggaCoGMKwxPQx7NwYl4FcMmxZcicc8wk6vI1iTxl0tsPKgW/YG8FgueQR+X7ZtZqw==
dependencies:
"@babel/runtime" "^7.0.0"
fbjs "^1.0.0"
relay-runtime@10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-10.1.0.tgz#4753bf36e95e8d862cef33608e3d98b4ed730d16"
@ -11987,7 +11937,7 @@ request-promise-native@^1.0.5:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.87.0, request@^2.88.0, request@^2.88.2:
request@^2.87.0, request@^2.88.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -12267,7 +12217,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -12411,6 +12361,15 @@ send@0.17.1:
range-parser "~1.2.1"
statuses "~1.5.0"
sentence-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f"
integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
upper-case-first "^2.0.2"
serialize-javascript@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
@ -12616,6 +12575,14 @@ smooth-scroll-into-view-if-needed@^1.1.27:
dependencies:
scroll-into-view-if-needed "^2.2.26"
snake-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@ -12778,6 +12745,13 @@ split-string@^3.0.1, split-string@^3.0.2:
dependencies:
extend-shallow "^3.0.0"
sponge-case@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c"
integrity sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==
dependencies:
tslib "^2.0.3"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -13081,7 +13055,7 @@ style-loader@0.23.1:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
styled-components@^5.0.1, styled-components@^5.1.0:
styled-components@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a"
integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==
@ -13156,6 +13130,13 @@ svgo@^1.0.0, svgo@^1.2.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
swap-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-2.0.2.tgz#671aedb3c9c137e2985ef51c51f9e98445bf70d9"
integrity sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==
dependencies:
tslib "^2.0.3"
symbol-observable@^1.0.2, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
@ -13298,6 +13279,13 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
title-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
integrity sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==
dependencies:
tslib "^2.0.3"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -13462,11 +13450,16 @@ tslib@^1.10.0, tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@~2.0.0, tslib@~2.0.1:
tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@~2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==
tslib@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
@ -13704,6 +13697,13 @@ upath@^1.1.1:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
upper-case-first@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324"
integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==
dependencies:
tslib "^2.0.3"
upper-case@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.1.tgz#6214d05e235dc817822464ccbae85822b3d8665f"
@ -14092,7 +14092,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.1:
whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.1:
version "3.5.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==

View File

@ -57,6 +57,7 @@ type Project struct {
TeamID uuid.UUID `json:"team_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
PublicOn sql.NullTime `json:"public_on"`
}
type ProjectLabel struct {

View File

@ -5,13 +5,14 @@ package db
import (
"context"
"database/sql"
"time"
"github.com/google/uuid"
)
const createPersonalProject = `-- name: CreatePersonalProject :one
INSERT INTO project(team_id, created_at, name) VALUES (null, $1, $2) RETURNING project_id, team_id, created_at, name
INSERT INTO project(team_id, created_at, name) VALUES (null, $1, $2) RETURNING project_id, team_id, created_at, name, public_on
`
type CreatePersonalProjectParams struct {
@ -27,6 +28,7 @@ func (q *Queries) CreatePersonalProject(ctx context.Context, arg CreatePersonalP
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
)
return i, err
}
@ -78,7 +80,7 @@ func (q *Queries) CreateProjectMember(ctx context.Context, arg CreateProjectMemb
}
const createTeamProject = `-- name: CreateTeamProject :one
INSERT INTO project(team_id, created_at, name) VALUES ($1, $2, $3) RETURNING project_id, team_id, created_at, name
INSERT INTO project(team_id, created_at, name) VALUES ($1, $2, $3) RETURNING project_id, team_id, created_at, name, public_on
`
type CreateTeamProjectParams struct {
@ -95,6 +97,7 @@ func (q *Queries) CreateTeamProject(ctx context.Context, arg CreateTeamProjectPa
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
)
return i, err
}
@ -132,7 +135,7 @@ func (q *Queries) DeleteProjectMember(ctx context.Context, arg DeleteProjectMemb
}
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
SELECT project_id, team_id, created_at, name FROM project WHERE team_id = $1
SELECT project_id, team_id, created_at, name, public_on FROM project WHERE team_id = $1
`
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
@ -149,6 +152,7 @@ func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) (
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
); err != nil {
return nil, err
}
@ -164,7 +168,7 @@ func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) (
}
const getAllTeamProjects = `-- name: GetAllTeamProjects :many
SELECT project_id, team_id, created_at, name FROM project WHERE team_id IS NOT null
SELECT project_id, team_id, created_at, name, public_on FROM project WHERE team_id IS NOT null
`
func (q *Queries) GetAllTeamProjects(ctx context.Context) ([]Project, error) {
@ -181,6 +185,7 @@ func (q *Queries) GetAllTeamProjects(ctx context.Context) ([]Project, error) {
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
); err != nil {
return nil, err
}
@ -196,7 +201,7 @@ func (q *Queries) GetAllTeamProjects(ctx context.Context) ([]Project, error) {
}
const getAllVisibleProjectsForUserID = `-- name: GetAllVisibleProjectsForUserID :many
SELECT project.project_id, project.team_id, project.created_at, project.name FROM project LEFT JOIN
SELECT project.project_id, project.team_id, project.created_at, project.name, project.public_on FROM project LEFT JOIN
project_member ON project_member.project_id = project.project_id WHERE project_member.user_id = $1
`
@ -214,6 +219,7 @@ func (q *Queries) GetAllVisibleProjectsForUserID(ctx context.Context, userID uui
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
); err != nil {
return nil, err
}
@ -292,7 +298,7 @@ func (q *Queries) GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.
}
const getPersonalProjectsForUserID = `-- name: GetPersonalProjectsForUserID :many
SELECT project.project_id, project.team_id, project.created_at, project.name FROM project
SELECT project.project_id, project.team_id, project.created_at, project.name, project.public_on FROM project
LEFT JOIN personal_project ON personal_project.project_id = project.project_id
WHERE personal_project.user_id = $1
`
@ -311,6 +317,7 @@ func (q *Queries) GetPersonalProjectsForUserID(ctx context.Context, userID uuid.
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
); err != nil {
return nil, err
}
@ -326,7 +333,7 @@ func (q *Queries) GetPersonalProjectsForUserID(ctx context.Context, userID uuid.
}
const getProjectByID = `-- name: GetProjectByID :one
SELECT project_id, team_id, created_at, name FROM project WHERE project_id = $1
SELECT project_id, team_id, created_at, name, public_on FROM project WHERE project_id = $1
`
func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) {
@ -337,6 +344,7 @@ func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Proj
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
)
return i, err
}
@ -426,6 +434,17 @@ func (q *Queries) GetProjectRolesForUserID(ctx context.Context, userID uuid.UUID
return items, nil
}
const getPublicOn = `-- name: GetPublicOn :one
SELECT public_on FROM project WHERE project_id = $1
`
func (q *Queries) GetPublicOn(ctx context.Context, projectID uuid.UUID) (sql.NullTime, error) {
row := q.db.QueryRowContext(ctx, getPublicOn, projectID)
var public_on sql.NullTime
err := row.Scan(&public_on)
return public_on, err
}
const getRoleForProjectMemberByUserID = `-- name: GetRoleForProjectMemberByUserID :one
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
@ -469,6 +488,28 @@ func (q *Queries) GetUserRolesForProject(ctx context.Context, arg GetUserRolesFo
return i, err
}
const setPublicOn = `-- name: SetPublicOn :one
UPDATE project SET public_on = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, public_on
`
type SetPublicOnParams struct {
ProjectID uuid.UUID `json:"project_id"`
PublicOn sql.NullTime `json:"public_on"`
}
func (q *Queries) SetPublicOn(ctx context.Context, arg SetPublicOnParams) (Project, error) {
row := q.db.QueryRowContext(ctx, setPublicOn, arg.ProjectID, arg.PublicOn)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
)
return i, err
}
const updateProjectMemberRole = `-- name: UpdateProjectMemberRole :one
UPDATE project_member SET role_code = $3 WHERE project_id = $1 AND user_id = $2
RETURNING project_member_id, project_id, user_id, added_at, role_code
@ -494,7 +535,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
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, public_on
`
type UpdateProjectNameByIDParams struct {
@ -510,6 +551,7 @@ func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNa
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.PublicOn,
)
return i, err
}

View File

@ -4,6 +4,7 @@ package db
import (
"context"
"database/sql"
"github.com/google/uuid"
)
@ -100,6 +101,7 @@ type Querier interface {
GetProjectMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectMember, error)
GetProjectRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetProjectRolesForUserIDRow, error)
GetProjectsForInvitedMember(ctx context.Context, email string) ([]uuid.UUID, error)
GetPublicOn(ctx context.Context, projectID uuid.UUID) (sql.NullTime, error)
GetRecentlyAssignedTaskForUserID(ctx context.Context, arg GetRecentlyAssignedTaskForUserIDParams) ([]Task, error)
GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRoleForProjectMemberByUserIDParams) (Role, error)
GetRoleForTeamMember(ctx context.Context, arg GetRoleForTeamMemberParams) (Role, error)
@ -132,6 +134,7 @@ type Querier interface {
HasAnyUser(ctx context.Context) (bool, error)
SetFirstUserActive(ctx context.Context) (UserAccount, error)
SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error
SetPublicOn(ctx context.Context, arg SetPublicOnParams) (Project, 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)

View File

@ -77,3 +77,9 @@ SELECT p.team_id, COALESCE(tm.role_code, '') AS team_role, COALESCE(pm.role_code
-- name: CreatePersonalProjectLink :one
INSERT INTO personal_project (project_id, user_id) VALUES ($1, $2) RETURNING *;
-- name: SetPublicOn :one
UPDATE project SET public_on = $2 WHERE project_id = $1 RETURNING *;
-- name: GetPublicOn :one
SELECT public_on FROM project WHERE project_id = $1;

View File

@ -58,6 +58,7 @@ type ResolverRoot interface {
type DirectiveRoot struct {
HasRole func(ctx context.Context, obj interface{}, next graphql.Resolver, roles []RoleLevel, level ActionLevel, typeArg ObjectType) (res interface{}, err error)
RequiresUser func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error)
}
type ComplexityRoot struct {
@ -248,6 +249,7 @@ type ComplexityRoot struct {
SetTaskChecklistItemComplete func(childComplexity int, input SetTaskChecklistItemComplete) int
SetTaskComplete func(childComplexity int, input SetTaskComplete) int
SortTaskGroup func(childComplexity int, input SortTaskGroup) int
ToggleProjectVisibility func(childComplexity int, input ToggleProjectVisibility) int
ToggleTaskLabel func(childComplexity int, input ToggleTaskLabelInput) int
UnassignTask func(childComplexity int, input *UnassignTaskInput) int
UpdateProjectLabel func(childComplexity int, input UpdateProjectLabel) int
@ -327,6 +329,7 @@ type ComplexityRoot struct {
Members func(childComplexity int) int
Name func(childComplexity int) int
Permission func(childComplexity int) int
PublicOn func(childComplexity int) int
TaskGroups func(childComplexity int) int
Team func(childComplexity int) int
}
@ -476,6 +479,10 @@ type ComplexityRoot struct {
TeamID func(childComplexity int) int
}
ToggleProjectVisibilityPayload struct {
Project func(childComplexity int) int
}
ToggleTaskLabelPayload struct {
Active func(childComplexity int) int
Task func(childComplexity int) int
@ -547,6 +554,7 @@ type MutationResolver interface {
CreateProject(ctx context.Context, input NewProject) (*db.Project, error)
DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error)
UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*db.Project, error)
ToggleProjectVisibility(ctx context.Context, input ToggleProjectVisibility) (*ToggleProjectVisibilityPayload, error)
CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*db.ProjectLabel, error)
DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*db.ProjectLabel, error)
UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error)
@ -619,6 +627,7 @@ type ProjectResolver interface {
TaskGroups(ctx context.Context, obj *db.Project) ([]db.TaskGroup, error)
Members(ctx context.Context, obj *db.Project) ([]Member, error)
InvitedMembers(ctx context.Context, obj *db.Project) ([]InvitedMember, error)
PublicOn(ctx context.Context, obj *db.Project) (*time.Time, error)
Permission(ctx context.Context, obj *db.Project) (*ProjectPermission, error)
Labels(ctx context.Context, obj *db.Project) ([]db.ProjectLabel, error)
}
@ -1624,6 +1633,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.SortTaskGroup(childComplexity, args["input"].(SortTaskGroup)), true
case "Mutation.toggleProjectVisibility":
if e.complexity.Mutation.ToggleProjectVisibility == nil {
break
}
args, err := ec.field_Mutation_toggleProjectVisibility_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ToggleProjectVisibility(childComplexity, args["input"].(ToggleProjectVisibility)), true
case "Mutation.toggleTaskLabel":
if e.complexity.Mutation.ToggleTaskLabel == nil {
break
@ -2098,6 +2119,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Project.Permission(childComplexity), true
case "Project.publicOn":
if e.complexity.Project.PublicOn == nil {
break
}
return e.complexity.Project.PublicOn(childComplexity), true
case "Project.taskGroups":
if e.complexity.Project.TaskGroups == nil {
break
@ -2763,6 +2791,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TeamRole.TeamID(childComplexity), true
case "ToggleProjectVisibilityPayload.project":
if e.complexity.ToggleProjectVisibilityPayload.Project == nil {
break
}
return e.complexity.ToggleProjectVisibilityPayload.Project(childComplexity), true
case "ToggleTaskLabelPayload.active":
if e.complexity.ToggleTaskLabelPayload.Active == nil {
break
@ -3158,6 +3193,7 @@ type Project {
taskGroups: [TaskGroup!]!
members: [Member!]!
invitedMembers: [InvitedMember!]!
publicOn: Time
permission: ProjectPermission!
labels: [ProjectLabel!]!
}
@ -3294,6 +3330,7 @@ enum ObjectType {
}
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
directive @requiresUser on FIELD_DEFINITION
type Query {
organizations: [Organization!]!
@ -3301,7 +3338,7 @@ type Query {
invitedUsers: [InvitedUserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!):
Project! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: PROJECT)
Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
@ -3309,7 +3346,7 @@ type Query {
myTasks(input: MyTasks!): MyTasksPayload!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: MePayload!
me: MePayload
}
@ -3427,6 +3464,16 @@ extend type Mutation {
DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectName(input: UpdateProjectName):
Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
toggleProjectVisibility(input: ToggleProjectVisibility!): ToggleProjectVisibilityPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input ToggleProjectVisibility {
projectID: UUID!
isPublic: Boolean!
}
type ToggleProjectVisibilityPayload {
project: Project!
}
input NewProject {
@ -4558,6 +4605,21 @@ func (ec *executionContext) field_Mutation_sortTaskGroup_args(ctx context.Contex
return args, nil
}
func (ec *executionContext) field_Mutation_toggleProjectVisibility_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 ToggleProjectVisibility
if tmp, ok := rawArgs["input"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
arg0, err = ec.unmarshalNToggleProjectVisibility2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleProjectVisibility(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_toggleTaskLabel_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -7744,6 +7806,80 @@ func (ec *executionContext) _Mutation_updateProjectName(ctx context.Context, fie
return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐProject(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_toggleProjectVisibility(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_toggleProjectVisibility_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ToggleProjectVisibility(rctx, args["input"].(ToggleProjectVisibility))
}
directive1 := func(ctx context.Context) (interface{}, error) {
roles, err := ec.unmarshalNRoleLevel2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleLevelᚄ(ctx, []interface{}{"ADMIN"})
if err != nil {
return nil, err
}
level, err := ec.unmarshalNActionLevel2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActionLevel(ctx, "PROJECT")
if err != nil {
return nil, err
}
typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
if err != nil {
return nil, err
}
if ec.directives.HasRole == nil {
return nil, errors.New("directive hasRole is not implemented")
}
return ec.directives.HasRole(ctx, nil, directive0, roles, level, typeArg)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*ToggleProjectVisibilityPayload); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.ToggleProjectVisibilityPayload`, tmp)
})
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.(*ToggleProjectVisibilityPayload)
fc.Result = res
return ec.marshalNToggleProjectVisibilityPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleProjectVisibilityPayload(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createProjectLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -12601,6 +12737,38 @@ func (ec *executionContext) _Project_invitedMembers(ctx context.Context, field g
return ec.marshalNInvitedMember2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInvitedMemberᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Project_publicOn(ctx context.Context, field graphql.CollectedField, obj *db.Project) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Project",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: 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.Project().PublicOn(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*time.Time)
fc.Result = res
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _Project_permission(ctx context.Context, field graphql.CollectedField, obj *db.Project) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -13224,40 +13392,8 @@ func (ec *executionContext) _Query_findProject(ctx context.Context, field graphq
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().FindProject(rctx, args["input"].(FindProject))
}
directive1 := func(ctx context.Context) (interface{}, error) {
roles, err := ec.unmarshalNRoleLevel2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleLevelᚄ(ctx, []interface{}{"ADMIN", "MEMBER"})
if err != nil {
return nil, err
}
level, err := ec.unmarshalNActionLevel2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActionLevel(ctx, "PROJECT")
if err != nil {
return nil, err
}
typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
if err != nil {
return nil, err
}
if ec.directives.HasRole == nil {
return nil, errors.New("directive hasRole is not implemented")
}
return ec.directives.HasRole(ctx, nil, directive0, roles, level, typeArg)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*db.Project); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/db.Project`, tmp)
})
if err != nil {
ec.Error(ctx, err)
@ -13572,14 +13708,11 @@ func (ec *executionContext) _Query_me(ctx context.Context, field graphql.Collect
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*MePayload)
fc.Result = res
return ec.marshalNMePayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMePayload(ctx, field.Selections, res)
return ec.marshalOMePayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMePayload(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_notifications(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -15885,6 +16018,41 @@ func (ec *executionContext) _TeamRole_roleCode(ctx context.Context, field graphq
return ec.marshalNRoleCode2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleCode(ctx, field.Selections, res)
}
func (ec *executionContext) _ToggleProjectVisibilityPayload_project(ctx context.Context, field graphql.CollectedField, obj *ToggleProjectVisibilityPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ToggleProjectVisibilityPayload",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: 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.Project, 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ᚋtaskcafeᚋinternalᚋdbᚐProject(ctx, field.Selections, res)
}
func (ec *executionContext) _ToggleTaskLabelPayload_active(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -19238,6 +19406,34 @@ func (ec *executionContext) unmarshalInputTaskPositionUpdate(ctx context.Context
return it, nil
}
func (ec *executionContext) unmarshalInputToggleProjectVisibility(ctx context.Context, obj interface{}) (ToggleProjectVisibility, error) {
var it ToggleProjectVisibility
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "projectID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("projectID"))
it.ProjectID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
case "isPublic":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("isPublic"))
it.IsPublic, err = ec.unmarshalNBoolean2bool(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputToggleTaskLabelInput(ctx context.Context, obj interface{}) (ToggleTaskLabelInput, error) {
var it ToggleTaskLabelInput
var asMap = obj.(map[string]interface{})
@ -20841,6 +21037,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "toggleProjectVisibility":
out.Values[i] = ec._Mutation_toggleProjectVisibility(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "createProjectLabel":
out.Values[i] = ec._Mutation_createProjectLabel(ctx, field)
if out.Values[i] == graphql.Null {
@ -21541,6 +21742,17 @@ func (ec *executionContext) _Project(ctx context.Context, sel ast.SelectionSet,
}
return res
})
case "publicOn":
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._Project_publicOn(ctx, field, obj)
return res
})
case "permission":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
@ -21939,9 +22151,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
}()
res = ec._Query_me(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "notifications":
@ -22860,6 +23069,33 @@ func (ec *executionContext) _TeamRole(ctx context.Context, sel ast.SelectionSet,
return out
}
var toggleProjectVisibilityPayloadImplementors = []string{"ToggleProjectVisibilityPayload"}
func (ec *executionContext) _ToggleProjectVisibilityPayload(ctx context.Context, sel ast.SelectionSet, obj *ToggleProjectVisibilityPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, toggleProjectVisibilityPayloadImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ToggleProjectVisibilityPayload")
case "project":
out.Values[i] = ec._ToggleProjectVisibilityPayload_project(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 toggleTaskLabelPayloadImplementors = []string{"ToggleTaskLabelPayload"}
func (ec *executionContext) _ToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, obj *ToggleTaskLabelPayload) graphql.Marshaler {
@ -24186,20 +24422,6 @@ func (ec *executionContext) unmarshalNLogoutUser2githubᚗcomᚋjordanknottᚋta
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNMePayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMePayload(ctx context.Context, sel ast.SelectionSet, v MePayload) graphql.Marshaler {
return ec._MePayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNMePayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMePayload(ctx context.Context, sel ast.SelectionSet, v *MePayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._MePayload(ctx, sel, v)
}
func (ec *executionContext) marshalNMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMember(ctx context.Context, sel ast.SelectionSet, v Member) graphql.Marshaler {
return ec._Member(ctx, sel, &v)
}
@ -25468,6 +25690,25 @@ func (ec *executionContext) marshalNTime2ᚖtimeᚐTime(ctx context.Context, sel
return res
}
func (ec *executionContext) unmarshalNToggleProjectVisibility2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleProjectVisibility(ctx context.Context, v interface{}) (ToggleProjectVisibility, error) {
res, err := ec.unmarshalInputToggleProjectVisibility(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNToggleProjectVisibilityPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleProjectVisibilityPayload(ctx context.Context, sel ast.SelectionSet, v ToggleProjectVisibilityPayload) graphql.Marshaler {
return ec._ToggleProjectVisibilityPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNToggleProjectVisibilityPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleProjectVisibilityPayload(ctx context.Context, sel ast.SelectionSet, v *ToggleProjectVisibilityPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ToggleProjectVisibilityPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNToggleTaskLabelInput2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐToggleTaskLabelInput(ctx context.Context, v interface{}) (ToggleTaskLabelInput, error) {
res, err := ec.unmarshalInputToggleTaskLabelInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@ -26081,6 +26322,13 @@ func (ec *executionContext) unmarshalODeleteTaskComment2ᚖgithubᚗcomᚋjordan
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalOMePayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMePayload(ctx context.Context, sel ast.SelectionSet, v *MePayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._MePayload(ctx, sel, v)
}
func (ec *executionContext) marshalOProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v *ProfileIcon) graphql.Marshaler {
if v == nil {
return graphql.Null

View File

@ -46,6 +46,12 @@ func NewHandler(repo db.Repository, emailConfig utils.EmailConfig) http.Handler
}
*/
userID, ok := GetUserID(ctx)
if !ok {
return nil, errors.New("user must be logged in")
}
log.Info("has role")
var subjectID uuid.UUID
in := graphql.GetFieldContext(ctx).Args["input"]
val := reflect.ValueOf(in) // could be any underlying type
@ -78,7 +84,7 @@ func NewHandler(repo db.Repository, emailConfig utils.EmailConfig) http.Handler
// TODO: add config setting to disable personal projects
return next(ctx)
}
subjectID, ok := subjectField.Interface().(uuid.UUID)
subjectID, ok = subjectField.Interface().(uuid.UUID)
if !ok {
logger.New(ctx).Error("error while casting subject UUID")
return nil, errors.New("error while casting subject uuid")
@ -130,10 +136,6 @@ func NewHandler(repo db.Repository, emailConfig utils.EmailConfig) http.Handler
},
}
} 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 {
logger.New(ctx).WithError(err).Error("error while getting team roles for user ID")
@ -270,3 +272,23 @@ const (
TASK_CHECKLIST_ADDED int32 = 9
TASK_CHECKLIST_REMOVED int32 = 10
)
func NotAuthorized() error {
return &gqlerror.Error{
Message: "Not authorized",
Extensions: map[string]interface{}{
"code": "UNAUTHENTICATED",
},
}
}
func IsProjectPublic(ctx context.Context, repo db.Repository, projectID uuid.UUID) (bool, error) {
publicOn, err := repo.GetPublicOn(ctx, projectID)
if err != nil {
return false, err
}
if !publicOn.Valid {
return false, nil
}
return true, nil
}

View File

@ -448,6 +448,15 @@ type TeamRole struct {
RoleCode RoleCode `json:"roleCode"`
}
type ToggleProjectVisibility struct {
ProjectID uuid.UUID `json:"projectID"`
IsPublic bool `json:"isPublic"`
}
type ToggleProjectVisibilityPayload struct {
Project *db.Project `json:"project"`
}
type ToggleTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
ProjectLabelID uuid.UUID `json:"projectLabelID"`

View File

@ -119,6 +119,7 @@ type Project {
taskGroups: [TaskGroup!]!
members: [Member!]!
invitedMembers: [InvitedMember!]!
publicOn: Time
permission: ProjectPermission!
labels: [ProjectLabel!]!
}
@ -255,6 +256,7 @@ enum ObjectType {
}
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
directive @requiresUser on FIELD_DEFINITION
type Query {
organizations: [Organization!]!
@ -262,7 +264,7 @@ type Query {
invitedUsers: [InvitedUserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!):
Project! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: PROJECT)
Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
@ -270,7 +272,7 @@ type Query {
myTasks(input: MyTasks!): MyTasksPayload!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: MePayload!
me: MePayload
}
@ -388,6 +390,16 @@ extend type Mutation {
DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectName(input: UpdateProjectName):
Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
toggleProjectVisibility(input: ToggleProjectVisibility!): ToggleProjectVisibilityPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input ToggleProjectVisibility {
projectID: UUID!
isPublic: Boolean!
}
type ToggleProjectVisibilityPayload {
project: Project!
}
input NewProject {
@ -982,3 +994,4 @@ type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}

View File

@ -84,6 +84,15 @@ func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateP
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 *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*db.ProjectLabel, error) {
createdAt := time.Now().UTC()
@ -1206,6 +1215,13 @@ func (r *projectResolver) InvitedMembers(ctx context.Context, obj *db.Project) (
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"))
}
@ -1277,12 +1293,19 @@ func (r *queryResolver) FindUser(ctx context.Context, input FindUser) (*db.UserA
func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db.Project, error) {
logger.New(ctx).Info("finding project user")
_, isLoggedIn := GetUser(ctx)
if !isLoggedIn {
isPublic, _ := IsProjectPublic(ctx, r.Repository, input.ProjectID)
if !isPublic {
return &db.Project{}, NotAuthorized()
}
}
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
if err == sql.ErrNoRows {
return &db.Project{}, &gqlerror.Error{
Message: "Project not found",
Extensions: map[string]interface{}{
"code": "11-404",
"code": "NOT_FOUND",
},
}
}
@ -1497,7 +1520,7 @@ func (r *queryResolver) TaskGroups(ctx context.Context) ([]db.TaskGroup, error)
func (r *queryResolver) Me(ctx context.Context) (*MePayload, error) {
userID, ok := GetUserID(ctx)
if !ok {
return &MePayload{}, fmt.Errorf("internal server error")
return nil, nil
}
user, err := r.Repository.GetUserAccountByID(ctx, userID)
if err == sql.ErrNoRows {

View File

@ -119,6 +119,7 @@ type Project {
taskGroups: [TaskGroup!]!
members: [Member!]!
invitedMembers: [InvitedMember!]!
publicOn: Time
permission: ProjectPermission!
labels: [ProjectLabel!]!
}

View File

@ -25,6 +25,7 @@ enum ObjectType {
}
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
directive @requiresUser on FIELD_DEFINITION
type Query {
organizations: [Organization!]!
@ -32,7 +33,7 @@ type Query {
invitedUsers: [InvitedUserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!):
Project! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: PROJECT)
Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
@ -40,7 +41,7 @@ type Query {
myTasks(input: MyTasks!): MyTasksPayload!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: MePayload!
me: MePayload
}

View File

@ -4,6 +4,16 @@ extend type Mutation {
DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectName(input: UpdateProjectName):
Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
toggleProjectVisibility(input: ToggleProjectVisibility!): ToggleProjectVisibilityPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input ToggleProjectVisibility {
projectID: UUID!
isPublic: Boolean!
}
type ToggleProjectVisibilityPayload {
project: Project!
}
input NewProject {

View File

@ -18,6 +18,7 @@ type AuthenticationMiddleware struct {
// Middleware returns the middleware handler
func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info("middleware")
requestID := uuid.New()
foundToken := true
tokenRaw := ""
@ -25,42 +26,26 @@ func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler {
if err != nil {
if err == http.ErrNoCookie {
foundToken = false
} else {
log.WithError(err).Error("unknown error")
w.WriteHeader(http.StatusBadRequest)
return
}
}
if !foundToken {
token := r.Header.Get("Authorization")
if token == "" {
log.WithError(err).Error("no auth token found in cookie or authorization header")
w.WriteHeader(http.StatusBadRequest)
return
}
if token != "" {
tokenRaw = token
}
} else {
tokenRaw = c.Value
}
authTokenID := uuid.MustParse(tokenRaw)
authTokenID, err := uuid.Parse(tokenRaw)
log.Info("checking if logged in")
ctx := r.Context()
if err == nil {
token, err := m.repo.GetAuthTokenByID(r.Context(), authTokenID)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{
"data": {},
"errors": [
{
"extensions": {
"code": "UNAUTHENTICATED"
if err == nil {
ctx = context.WithValue(ctx, utils.UserIDKey, token.UserID)
}
}
]
}`))
return
}
ctx := context.WithValue(r.Context(), utils.UserIDKey, token.UserID)
// ctx = context.WithValue(ctx, utils.RestrictedModeKey, accessClaims.Restricted)
// ctx = context.WithValue(ctx, utils.OrgRoleKey, accessClaims.OrgRole)
ctx = context.WithValue(ctx, utils.ReqIDKey, requestID)

View File

@ -0,0 +1 @@
ALTER TABLE project ADD COLUMN public_on timestamptz;