Compare commits
	
		
			11 Commits
		
	
	
		
			feat/list-
			...
			feat/updat
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4a8d4a6ec3 | ||
| 
						 | 
					e4d1e21304 | ||
| 
						 | 
					f7c6ee470e | ||
| 
						 | 
					227ce5966d | ||
| 
						 | 
					aa5e1c0661 | ||
| 
						 | 
					b603081691 | ||
| 
						 | 
					e76ea9da63 | ||
| 
						 | 
					923d7f7372 | ||
| 
						 | 
					009d717d80 | ||
| 
						 | 
					4272fefa28 | ||
| 
						 | 
					25f5cad557 | 
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
### Added
 | 
			
		||||
- Task sorting & filtering
 | 
			
		||||
- Redesigned the Task Details UI
 | 
			
		||||
- Implement task group actions (duplicate/delete all tasks/sort)
 | 
			
		||||
 | 
			
		||||
### Fixed
 | 
			
		||||
- removed CORS middleware to fix security issue
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,8 @@ Was this project useful? Please consider <a href="https://www.buymeacoffee.com/j
 | 
			
		||||
 | 
			
		||||
**Please note that this project is still in active development. Some options may not work yet! For updates on development, join the Discord server**
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
Currently Taskcafe only offers basic task tracking through a Kanban board.
 | 
			
		||||
 
 | 
			
		||||
@@ -171,7 +171,7 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
 | 
			
		||||
 | 
			
		||||
const AdminRoute = () => {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    document.title = 'Taskcafé | Admin';
 | 
			
		||||
    document.title = 'Admin | Taskcafé';
 | 
			
		||||
  }, []);
 | 
			
		||||
  const { loading, data } = useUsersQuery();
 | 
			
		||||
  const { showPopup, hidePopup } = usePopup();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,20 @@ import styled from 'styled-components/macro';
 | 
			
		||||
import GlobalTopNavbar from 'App/TopNavbar';
 | 
			
		||||
import { getAccessToken } from 'shared/utils/accessToken';
 | 
			
		||||
import Settings from 'shared/components/Settings';
 | 
			
		||||
import { useMeQuery, useClearProfileAvatarMutation, useUpdateUserPasswordMutation } from 'shared/generated/graphql';
 | 
			
		||||
import {
 | 
			
		||||
  useMeQuery,
 | 
			
		||||
  useClearProfileAvatarMutation,
 | 
			
		||||
  useUpdateUserPasswordMutation,
 | 
			
		||||
  useUpdateUserInfoMutation,
 | 
			
		||||
  MeQuery,
 | 
			
		||||
  MeDocument,
 | 
			
		||||
} from 'shared/generated/graphql';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { useCurrentUser } from 'App/context';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import { toast } from 'react-toastify';
 | 
			
		||||
import updateApolloCache from 'shared/utils/cache';
 | 
			
		||||
import produce from 'immer';
 | 
			
		||||
 | 
			
		||||
const MainContent = styled.div`
 | 
			
		||||
  padding: 0 0 50px 80px;
 | 
			
		||||
@@ -19,6 +28,7 @@ const Projects = () => {
 | 
			
		||||
  const $fileUpload = useRef<HTMLInputElement>(null);
 | 
			
		||||
  const [clearProfileAvatar] = useClearProfileAvatarMutation();
 | 
			
		||||
  const { user } = useCurrentUser();
 | 
			
		||||
  const [updateUserInfo] = useUpdateUserInfoMutation();
 | 
			
		||||
  const [updateUserPassword] = useUpdateUserPasswordMutation();
 | 
			
		||||
  const { loading, data, refetch } = useMeQuery();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@@ -69,6 +79,13 @@ const Projects = () => {
 | 
			
		||||
            toast('Password was changed!');
 | 
			
		||||
            done();
 | 
			
		||||
          }}
 | 
			
		||||
          onChangeUserInfo={(d, done) => {
 | 
			
		||||
            updateUserInfo({
 | 
			
		||||
              variables: { name: d.full_name, bio: d.bio, email: d.email, initials: d.initials },
 | 
			
		||||
            });
 | 
			
		||||
            toast('User info was saved!');
 | 
			
		||||
            done();
 | 
			
		||||
          }}
 | 
			
		||||
          onProfileAvatarRemove={() => {
 | 
			
		||||
            clearProfileAvatar();
 | 
			
		||||
          }}
 | 
			
		||||
 
 | 
			
		||||
@@ -323,6 +323,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
 | 
			
		||||
  const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({});
 | 
			
		||||
  const { loading, data } = useFindProjectQuery({
 | 
			
		||||
    variables: { projectID },
 | 
			
		||||
    pollInterval: 5000,
 | 
			
		||||
  });
 | 
			
		||||
  const [deleteTaskGroupTasks] = useDeleteTaskGroupTasksMutation({
 | 
			
		||||
    update: (client, resp) =>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import Modal from 'shared/components/Modal';
 | 
			
		||||
import TaskDetails from 'shared/components/TaskDetails';
 | 
			
		||||
import { Popup, usePopup } from 'shared/components/PopupMenu';
 | 
			
		||||
import MemberManager from 'shared/components/MemberManager';
 | 
			
		||||
import { useRouteMatch, useHistory } from 'react-router';
 | 
			
		||||
import { useRouteMatch, useHistory, Redirect } from 'react-router';
 | 
			
		||||
import {
 | 
			
		||||
  useDeleteTaskChecklistMutation,
 | 
			
		||||
  useUpdateTaskChecklistNameMutation,
 | 
			
		||||
@@ -32,6 +32,7 @@ import Input from 'shared/components/Input';
 | 
			
		||||
import { useForm } from 'react-hook-form';
 | 
			
		||||
import updateApolloCache from 'shared/utils/cache';
 | 
			
		||||
import NOOP from 'shared/utils/noop';
 | 
			
		||||
import hasNotFoundError from 'shared/utils/error';
 | 
			
		||||
 | 
			
		||||
const calculateChecklistBadge = (checklists: Array<TaskChecklist>) => {
 | 
			
		||||
  const total = checklists.reduce((prev: any, next: any) => {
 | 
			
		||||
@@ -269,8 +270,8 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
 | 
			
		||||
  const [setTaskComplete] = useSetTaskCompleteMutation();
 | 
			
		||||
  const { loading, data, refetch, error } = useFindTaskQuery({ variables: { taskID }, pollInterval: 5000 });
 | 
			
		||||
  const [setTaskComplete, { error: setTaskCompleteError }] = useSetTaskCompleteMutation();
 | 
			
		||||
  const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
 | 
			
		||||
    onCompleted: () => {
 | 
			
		||||
      refetch();
 | 
			
		||||
@@ -289,9 +290,13 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
      refreshCache();
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return null;
 | 
			
		||||
  if (hasNotFoundError(error, setTaskCompleteError)) {
 | 
			
		||||
    return <Redirect to={projectURL} />;
 | 
			
		||||
  }
 | 
			
		||||
  if (setTaskCompleteError && setTaskCompleteError)
 | 
			
		||||
    if (loading) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  if (!data) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
@@ -346,7 +351,11 @@ const Details: React.FC<DetailsProps> = ({
 | 
			
		||||
              onTaskNameChange={onTaskNameChange}
 | 
			
		||||
              onTaskDescriptionChange={onTaskDescriptionChange}
 | 
			
		||||
              onToggleTaskComplete={task => {
 | 
			
		||||
                setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
 | 
			
		||||
                setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } }).catch(r => {
 | 
			
		||||
                  if (hasNotFoundError(r)) {
 | 
			
		||||
                    history.push(projectURL);
 | 
			
		||||
                  }
 | 
			
		||||
                });
 | 
			
		||||
              }}
 | 
			
		||||
              onDeleteTask={onDeleteTask}
 | 
			
		||||
              onChangeItemName={(itemID, itemName) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -234,7 +234,7 @@ type ShowNewProject = {
 | 
			
		||||
 | 
			
		||||
const Projects = () => {
 | 
			
		||||
  const { showPopup, hidePopup } = usePopup();
 | 
			
		||||
  const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only' });
 | 
			
		||||
  const { loading, data } = useGetProjectsQuery({ fetchPolicy: 'network-only', pollInterval: 5000 });
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    document.title = 'Taskcafé';
 | 
			
		||||
  }, []);
 | 
			
		||||
@@ -260,11 +260,7 @@ const Projects = () => {
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        <span>loading</span>
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
    return <GlobalTopNavbar onSaveProjectName={NOOP} projectID={null} name={null} />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f'];
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,7 @@ type TeamProjectsProps = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const TeamProjects: React.FC<TeamProjectsProps> = ({ teamID }) => {
 | 
			
		||||
  const { loading, data } = useGetTeamQuery({ variables: { teamID } });
 | 
			
		||||
  const { loading, data } = useGetTeamQuery({ variables: { teamID }, pollInterval: 5000 });
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return <span>loading</span>;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -85,18 +85,30 @@ type TeamsRouteProps = {
 | 
			
		||||
const Teams = () => {
 | 
			
		||||
  const { teamID } = useParams<TeamsRouteProps>();
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const { loading, data } = useGetTeamQuery({ variables: { teamID } });
 | 
			
		||||
  const { loading, data } = useGetTeamQuery({
 | 
			
		||||
    variables: { teamID },
 | 
			
		||||
    onCompleted: resp => {
 | 
			
		||||
      document.title = `${resp.findTeam.name} | Taskcafé`;
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  const { user } = useCurrentUser();
 | 
			
		||||
  const [currentTab, setCurrentTab] = useState(0);
 | 
			
		||||
  const match = useRouteMatch();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    document.title = 'Teams | Taskcafé';
 | 
			
		||||
  }, []);
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        <span>loading</span>
 | 
			
		||||
      </>
 | 
			
		||||
      <GlobalTopNavbar
 | 
			
		||||
        menuType={[
 | 
			
		||||
          { name: 'Projects', link: `${match.url}` },
 | 
			
		||||
          { name: 'Members', link: `${match.url}/members` },
 | 
			
		||||
        ]}
 | 
			
		||||
        currentTab={currentTab}
 | 
			
		||||
        onSetTab={tab => {
 | 
			
		||||
          setCurrentTab(tab);
 | 
			
		||||
        }}
 | 
			
		||||
        onSaveProjectName={NOOP}
 | 
			
		||||
        projectID={null}
 | 
			
		||||
        name={null}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (data && user) {
 | 
			
		||||
 
 | 
			
		||||
@@ -557,6 +557,7 @@ const Admin: React.FC<AdminProps> = ({
 | 
			
		||||
        <TabNavContent>
 | 
			
		||||
          {items.map((item, idx) => (
 | 
			
		||||
            <NavItem
 | 
			
		||||
              key={item.name}
 | 
			
		||||
              onClick={(tab, top) => {
 | 
			
		||||
                if ($tabNav && $tabNav.current) {
 | 
			
		||||
                  const pos = $tabNav.current.getBoundingClientRect();
 | 
			
		||||
 
 | 
			
		||||
@@ -147,6 +147,11 @@ export const ListCardLabelText = styled.span`
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ListCardLabelsWrapper = styled.div`
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
  position: relative;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
 | 
			
		||||
  ${props =>
 | 
			
		||||
    props.variant === 'small'
 | 
			
		||||
@@ -178,8 +183,6 @@ export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirection: 'expand' | 'shrink' }>`
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    opacity: 0.8;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import {
 | 
			
		||||
  ListCardLabels,
 | 
			
		||||
  ListCardLabel,
 | 
			
		||||
  ListCardLabelText,
 | 
			
		||||
  ListCardLabelsWrapper,
 | 
			
		||||
  ListCardOperation,
 | 
			
		||||
  CardTitle,
 | 
			
		||||
  CardMembers,
 | 
			
		||||
@@ -158,35 +159,38 @@ const Card = React.forwardRef(
 | 
			
		||||
            </ListCardOperation>
 | 
			
		||||
          )}
 | 
			
		||||
          <ListCardDetails complete={complete ?? false}>
 | 
			
		||||
            <ListCardLabels
 | 
			
		||||
              toggleLabels={toggleLabels}
 | 
			
		||||
              toggleDirection={toggleDirection}
 | 
			
		||||
              onClick={e => {
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                if (onCardLabelClick) {
 | 
			
		||||
                  onCardLabelClick();
 | 
			
		||||
                }
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {labels &&
 | 
			
		||||
                labels
 | 
			
		||||
                  .slice()
 | 
			
		||||
                  .sort((a, b) => a.labelColor.position - b.labelColor.position)
 | 
			
		||||
                  .map(label => (
 | 
			
		||||
                    <ListCardLabel
 | 
			
		||||
                      onAnimationEnd={() => {
 | 
			
		||||
                        if (setToggleLabels) {
 | 
			
		||||
                          setToggleLabels(false);
 | 
			
		||||
                        }
 | 
			
		||||
                      }}
 | 
			
		||||
                      variant={labelVariant ?? 'large'}
 | 
			
		||||
                      color={label.labelColor.colorHex}
 | 
			
		||||
                      key={label.id}
 | 
			
		||||
                    >
 | 
			
		||||
                      <ListCardLabelText>{label.name}</ListCardLabelText>
 | 
			
		||||
                    </ListCardLabel>
 | 
			
		||||
                  ))}
 | 
			
		||||
            </ListCardLabels>
 | 
			
		||||
            {labels && labels.length !== 0 && (
 | 
			
		||||
              <ListCardLabelsWrapper>
 | 
			
		||||
                <ListCardLabels
 | 
			
		||||
                  toggleLabels={toggleLabels}
 | 
			
		||||
                  toggleDirection={toggleDirection}
 | 
			
		||||
                  onClick={e => {
 | 
			
		||||
                    e.stopPropagation();
 | 
			
		||||
                    if (onCardLabelClick) {
 | 
			
		||||
                      onCardLabelClick();
 | 
			
		||||
                    }
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {labels
 | 
			
		||||
                    .slice()
 | 
			
		||||
                    .sort((a, b) => a.labelColor.position - b.labelColor.position)
 | 
			
		||||
                    .map(label => (
 | 
			
		||||
                      <ListCardLabel
 | 
			
		||||
                        onAnimationEnd={() => {
 | 
			
		||||
                          if (setToggleLabels) {
 | 
			
		||||
                            setToggleLabels(false);
 | 
			
		||||
                          }
 | 
			
		||||
                        }}
 | 
			
		||||
                        variant={labelVariant ?? 'large'}
 | 
			
		||||
                        color={label.labelColor.colorHex}
 | 
			
		||||
                        key={label.id}
 | 
			
		||||
                      >
 | 
			
		||||
                        <ListCardLabelText>{label.name}</ListCardLabelText>
 | 
			
		||||
                      </ListCardLabel>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </ListCardLabels>
 | 
			
		||||
              </ListCardLabelsWrapper>
 | 
			
		||||
            )}
 | 
			
		||||
            {editable ? (
 | 
			
		||||
              <EditorContent>
 | 
			
		||||
                {complete && <CompleteIcon width={16} height={16} />}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ const Icon = styled.div`
 | 
			
		||||
 | 
			
		||||
type InputProps = {
 | 
			
		||||
  variant?: 'normal' | 'alternate';
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  label?: string;
 | 
			
		||||
  width?: string;
 | 
			
		||||
  floatingLabel?: boolean;
 | 
			
		||||
@@ -116,6 +117,7 @@ function useCombinedRefs(...refs: any) {
 | 
			
		||||
const Input = React.forwardRef(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      disabled = false,
 | 
			
		||||
      width = 'auto',
 | 
			
		||||
      variant = 'normal',
 | 
			
		||||
      type = 'text',
 | 
			
		||||
@@ -160,6 +162,7 @@ const Input = React.forwardRef(
 | 
			
		||||
          onChange={e => {
 | 
			
		||||
            setHasValue((e.currentTarget.value !== '' || floatingLabel) ?? false);
 | 
			
		||||
          }}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          hasValue={hasValue}
 | 
			
		||||
          ref={combinedRef}
 | 
			
		||||
          id={id}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ export const Default = () => {
 | 
			
		||||
      <BaseStyles />
 | 
			
		||||
      <Settings
 | 
			
		||||
        profile={profile}
 | 
			
		||||
        onChangeUserInfo={action('change user info')}
 | 
			
		||||
        onResetPassword={action('reset password')}
 | 
			
		||||
        onProfileAvatarRemove={action('remove')}
 | 
			
		||||
        onProfileAvatarChange={action('profile avatar change')}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,11 @@ const PasswordInput = styled(Input)`
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const UserInfoInput = styled(Input)`
 | 
			
		||||
  margin-top: 30px;
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const FormError = styled.span`
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: rgba(${props => props.theme.colors.warning});
 | 
			
		||||
@@ -240,6 +245,7 @@ const SaveButton = styled(Button)`
 | 
			
		||||
type SettingsProps = {
 | 
			
		||||
  onProfileAvatarChange: () => void;
 | 
			
		||||
  onProfileAvatarRemove: () => void;
 | 
			
		||||
  onChangeUserInfo: (data: UserInfoData, done: () => void) => void;
 | 
			
		||||
  onResetPassword: (password: string, done: () => void) => void;
 | 
			
		||||
  profile: TaskUser;
 | 
			
		||||
};
 | 
			
		||||
@@ -300,9 +306,93 @@ const ResetPasswordTab: React.FC<ResetPasswordTabProps> = ({ onResetPassword })
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type UserInfoData = {
 | 
			
		||||
  full_name: string;
 | 
			
		||||
  bio: string;
 | 
			
		||||
  initials: string;
 | 
			
		||||
  email: string;
 | 
			
		||||
};
 | 
			
		||||
type UserInfoTabProps = {
 | 
			
		||||
  profile: TaskUser;
 | 
			
		||||
  onProfileAvatarChange: () => void;
 | 
			
		||||
  onProfileAvatarRemove: () => void;
 | 
			
		||||
  onChangeUserInfo: (data: UserInfoData, done: () => void) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const EMAIL_PATTERN = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i;
 | 
			
		||||
const INITIALS_PATTERN = /^[a-zA-Z]{2,3}$/i;
 | 
			
		||||
 | 
			
		||||
const UserInfoTab: React.FC<UserInfoTabProps> = ({
 | 
			
		||||
  profile,
 | 
			
		||||
  onProfileAvatarRemove,
 | 
			
		||||
  onProfileAvatarChange,
 | 
			
		||||
  onChangeUserInfo,
 | 
			
		||||
}) => {
 | 
			
		||||
  const [active, setActive] = useState(true);
 | 
			
		||||
  const { register, handleSubmit, errors } = useForm<UserInfoData>();
 | 
			
		||||
  const done = () => {
 | 
			
		||||
    setActive(true);
 | 
			
		||||
  };
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <AvatarSettings
 | 
			
		||||
        onProfileAvatarRemove={onProfileAvatarRemove}
 | 
			
		||||
        onProfileAvatarChange={onProfileAvatarChange}
 | 
			
		||||
        profile={profile.profileIcon}
 | 
			
		||||
      />
 | 
			
		||||
      <form
 | 
			
		||||
        onSubmit={handleSubmit(data => {
 | 
			
		||||
          setActive(false);
 | 
			
		||||
          onChangeUserInfo(data, done);
 | 
			
		||||
        })}
 | 
			
		||||
      >
 | 
			
		||||
        <UserInfoInput
 | 
			
		||||
          ref={register({ required: 'Full name is required' })}
 | 
			
		||||
          name="full_name"
 | 
			
		||||
          defaultValue={profile.fullName}
 | 
			
		||||
          width="100%"
 | 
			
		||||
          label="Name"
 | 
			
		||||
        />
 | 
			
		||||
        {errors.full_name && <FormError>{errors.full_name.message}</FormError>}
 | 
			
		||||
        <UserInfoInput
 | 
			
		||||
          defaultValue={profile.profileIcon && profile.profileIcon.initials ? profile.profileIcon.initials : ''}
 | 
			
		||||
          ref={register({
 | 
			
		||||
            required: 'Initials is required',
 | 
			
		||||
            pattern: { value: INITIALS_PATTERN, message: 'Intials must be between two to four characters' },
 | 
			
		||||
          })}
 | 
			
		||||
          name="initials"
 | 
			
		||||
          width="100%"
 | 
			
		||||
          label="Initials "
 | 
			
		||||
        />
 | 
			
		||||
        {errors.initials && <FormError>{errors.initials.message}</FormError>}
 | 
			
		||||
        <UserInfoInput disabled defaultValue={profile.username ?? ''} width="100%" label="Username " />
 | 
			
		||||
        <UserInfoInput
 | 
			
		||||
          width="100%"
 | 
			
		||||
          name="email"
 | 
			
		||||
          ref={register({
 | 
			
		||||
            required: 'Email is required',
 | 
			
		||||
            pattern: { value: EMAIL_PATTERN, message: 'Must be a valid email' },
 | 
			
		||||
          })}
 | 
			
		||||
          defaultValue={profile.email ?? ''}
 | 
			
		||||
          label="Email"
 | 
			
		||||
        />
 | 
			
		||||
        {errors.email && <FormError>{errors.email.message}</FormError>}
 | 
			
		||||
        <UserInfoInput width="100%" name="bio" ref={register()} defaultValue={profile.bio ?? ''} label="Bio" />
 | 
			
		||||
        {errors.bio && <FormError>{errors.bio.message}</FormError>}
 | 
			
		||||
        <SettingActions>
 | 
			
		||||
          <SaveButton disabled={!active} type="submit">
 | 
			
		||||
            Save Change
 | 
			
		||||
          </SaveButton>
 | 
			
		||||
        </SettingActions>
 | 
			
		||||
      </form>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Settings: React.FC<SettingsProps> = ({
 | 
			
		||||
  onProfileAvatarRemove,
 | 
			
		||||
  onProfileAvatarChange,
 | 
			
		||||
  onChangeUserInfo,
 | 
			
		||||
  onResetPassword,
 | 
			
		||||
  profile,
 | 
			
		||||
}) => {
 | 
			
		||||
@@ -315,6 +405,7 @@ const Settings: React.FC<SettingsProps> = ({
 | 
			
		||||
        <TabNavContent>
 | 
			
		||||
          {items.map((item, idx) => (
 | 
			
		||||
            <NavItem
 | 
			
		||||
              key={item.name}
 | 
			
		||||
              onClick={(tab, top) => {
 | 
			
		||||
                if ($tabNav && $tabNav.current) {
 | 
			
		||||
                  const pos = $tabNav.current.getBoundingClientRect();
 | 
			
		||||
@@ -332,23 +423,12 @@ const Settings: React.FC<SettingsProps> = ({
 | 
			
		||||
      </TabNav>
 | 
			
		||||
      <TabContentWrapper>
 | 
			
		||||
        <Tab tab={0} currentTab={currentTab}>
 | 
			
		||||
          <AvatarSettings
 | 
			
		||||
            onProfileAvatarRemove={onProfileAvatarRemove}
 | 
			
		||||
          <UserInfoTab
 | 
			
		||||
            onProfileAvatarChange={onProfileAvatarChange}
 | 
			
		||||
            profile={profile.profileIcon}
 | 
			
		||||
            onProfileAvatarRemove={onProfileAvatarRemove}
 | 
			
		||||
            profile={profile}
 | 
			
		||||
            onChangeUserInfo={onChangeUserInfo}
 | 
			
		||||
          />
 | 
			
		||||
          <Input defaultValue={profile.fullName} width="100%" label="Name" />
 | 
			
		||||
          <Input
 | 
			
		||||
            defaultValue={profile.profileIcon && profile.profileIcon.initials ? profile.profileIcon.initials : ''}
 | 
			
		||||
            width="100%"
 | 
			
		||||
            label="Initials "
 | 
			
		||||
          />
 | 
			
		||||
          <Input defaultValue={profile.username ?? ''} width="100%" label="Username " />
 | 
			
		||||
          <Input width="100%" label="Email" />
 | 
			
		||||
          <Input width="100%" label="Bio" />
 | 
			
		||||
          <SettingActions>
 | 
			
		||||
            <SaveButton>Save Change</SaveButton>
 | 
			
		||||
          </SettingActions>
 | 
			
		||||
        </Tab>
 | 
			
		||||
        <Tab tab={1} currentTab={currentTab}>
 | 
			
		||||
          <ResetPasswordTab onResetPassword={onResetPassword} />
 | 
			
		||||
 
 | 
			
		||||
@@ -169,7 +169,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
 | 
			
		||||
          <LeftSidebarSection>
 | 
			
		||||
            <SidebarTitle>TASK GROUP</SidebarTitle>
 | 
			
		||||
            <SidebarButton>
 | 
			
		||||
              <SidebarButtonText>Release 0.1.0</SidebarButtonText>
 | 
			
		||||
              <SidebarButtonText>{task.taskGroup.name}</SidebarButtonText>
 | 
			
		||||
            </SidebarButton>
 | 
			
		||||
            <DueDateTitle>DUE DATE</DueDateTitle>
 | 
			
		||||
            <SidebarButton
 | 
			
		||||
 
 | 
			
		||||
@@ -251,8 +251,8 @@ export const NavSeparator = styled.div`
 | 
			
		||||
export const LogoContainer = styled(Link)`
 | 
			
		||||
  display: block;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  right: 50%;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transform: translateX(-50%);
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,7 @@ export type UserAccount = {
 | 
			
		||||
  createdAt: Scalars['Time'];
 | 
			
		||||
  fullName: Scalars['String'];
 | 
			
		||||
  initials: Scalars['String'];
 | 
			
		||||
  bio: Scalars['String'];
 | 
			
		||||
  role: Role;
 | 
			
		||||
  username: Scalars['String'];
 | 
			
		||||
  profileIcon: ProfileIcon;
 | 
			
		||||
@@ -208,7 +209,8 @@ export enum ObjectType {
 | 
			
		||||
  Org = 'ORG',
 | 
			
		||||
  Team = 'TEAM',
 | 
			
		||||
  Project = 'PROJECT',
 | 
			
		||||
  Task = 'TASK'
 | 
			
		||||
  Task = 'TASK',
 | 
			
		||||
  TaskGroup = 'TASK_GROUP'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Query = {
 | 
			
		||||
@@ -303,6 +305,7 @@ export type Mutation = {
 | 
			
		||||
  updateTaskLocation: UpdateTaskLocationPayload;
 | 
			
		||||
  updateTaskName: Task;
 | 
			
		||||
  updateTeamMemberRole: UpdateTeamMemberRolePayload;
 | 
			
		||||
  updateUserInfo: UpdateUserInfoPayload;
 | 
			
		||||
  updateUserPassword: UpdateUserPasswordPayload;
 | 
			
		||||
  updateUserRole: UpdateUserRolePayload;
 | 
			
		||||
};
 | 
			
		||||
@@ -548,6 +551,11 @@ export type MutationUpdateTeamMemberRoleArgs = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type MutationUpdateUserInfoArgs = {
 | 
			
		||||
  input: UpdateUserInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type MutationUpdateUserPasswordArgs = {
 | 
			
		||||
  input: UpdateUserPassword;
 | 
			
		||||
};
 | 
			
		||||
@@ -715,7 +723,7 @@ export type UpdateProjectMemberRolePayload = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type NewTask = {
 | 
			
		||||
  taskGroupID: Scalars['String'];
 | 
			
		||||
  taskGroupID: Scalars['UUID'];
 | 
			
		||||
  name: Scalars['String'];
 | 
			
		||||
  position: Scalars['Float'];
 | 
			
		||||
};
 | 
			
		||||
@@ -979,6 +987,18 @@ export type UpdateTeamMemberRolePayload = {
 | 
			
		||||
  member: Member;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UpdateUserInfoPayload = {
 | 
			
		||||
   __typename?: 'UpdateUserInfoPayload';
 | 
			
		||||
  user: UserAccount;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UpdateUserInfo = {
 | 
			
		||||
  name: Scalars['String'];
 | 
			
		||||
  initials: Scalars['String'];
 | 
			
		||||
  email: Scalars['String'];
 | 
			
		||||
  bio: Scalars['String'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UpdateUserPassword = {
 | 
			
		||||
  userID: Scalars['UUID'];
 | 
			
		||||
  password: Scalars['String'];
 | 
			
		||||
@@ -1245,7 +1265,7 @@ export type FindTaskQuery = (
 | 
			
		||||
    & Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'position' | 'complete'>
 | 
			
		||||
    & { taskGroup: (
 | 
			
		||||
      { __typename?: 'TaskGroup' }
 | 
			
		||||
      & Pick<TaskGroup, 'id'>
 | 
			
		||||
      & Pick<TaskGroup, 'id' | 'name'>
 | 
			
		||||
    ), badges: (
 | 
			
		||||
      { __typename?: 'TaskBadges' }
 | 
			
		||||
      & { checklist?: Maybe<(
 | 
			
		||||
@@ -1354,7 +1374,7 @@ export type MeQuery = (
 | 
			
		||||
    { __typename?: 'MePayload' }
 | 
			
		||||
    & { user: (
 | 
			
		||||
      { __typename?: 'UserAccount' }
 | 
			
		||||
      & Pick<UserAccount, 'id' | 'fullName'>
 | 
			
		||||
      & Pick<UserAccount, 'id' | 'fullName' | 'username' | 'email' | 'bio'>
 | 
			
		||||
      & { profileIcon: (
 | 
			
		||||
        { __typename?: 'ProfileIcon' }
 | 
			
		||||
        & Pick<ProfileIcon, 'initials' | 'bgColor' | 'url'>
 | 
			
		||||
@@ -1453,7 +1473,7 @@ export type UpdateProjectMemberRoleMutation = (
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type CreateTaskMutationVariables = {
 | 
			
		||||
  taskGroupID: Scalars['String'];
 | 
			
		||||
  taskGroupID: Scalars['UUID'];
 | 
			
		||||
  name: Scalars['String'];
 | 
			
		||||
  position: Scalars['Float'];
 | 
			
		||||
};
 | 
			
		||||
@@ -2080,7 +2100,7 @@ export type CreateUserAccountMutation = (
 | 
			
		||||
  { __typename?: 'Mutation' }
 | 
			
		||||
  & { createUserAccount: (
 | 
			
		||||
    { __typename?: 'UserAccount' }
 | 
			
		||||
    & Pick<UserAccount, 'id' | 'email' | 'fullName' | 'initials' | 'username'>
 | 
			
		||||
    & Pick<UserAccount, 'id' | 'email' | 'fullName' | 'initials' | 'username' | 'bio'>
 | 
			
		||||
    & { profileIcon: (
 | 
			
		||||
      { __typename?: 'ProfileIcon' }
 | 
			
		||||
      & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
 | 
			
		||||
@@ -2127,6 +2147,29 @@ export type DeleteUserAccountMutation = (
 | 
			
		||||
  ) }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type UpdateUserInfoMutationVariables = {
 | 
			
		||||
  name: Scalars['String'];
 | 
			
		||||
  initials: Scalars['String'];
 | 
			
		||||
  email: Scalars['String'];
 | 
			
		||||
  bio: Scalars['String'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type UpdateUserInfoMutation = (
 | 
			
		||||
  { __typename?: 'Mutation' }
 | 
			
		||||
  & { updateUserInfo: (
 | 
			
		||||
    { __typename?: 'UpdateUserInfoPayload' }
 | 
			
		||||
    & { user: (
 | 
			
		||||
      { __typename?: 'UserAccount' }
 | 
			
		||||
      & Pick<UserAccount, 'id' | 'email' | 'fullName' | 'bio'>
 | 
			
		||||
      & { profileIcon: (
 | 
			
		||||
        { __typename?: 'ProfileIcon' }
 | 
			
		||||
        & Pick<ProfileIcon, 'initials'>
 | 
			
		||||
      ) }
 | 
			
		||||
    ) }
 | 
			
		||||
  ) }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type UpdateUserPasswordMutationVariables = {
 | 
			
		||||
  userID: Scalars['UUID'];
 | 
			
		||||
  password: Scalars['String'];
 | 
			
		||||
@@ -2660,6 +2703,7 @@ export const FindTaskDocument = gql`
 | 
			
		||||
    complete
 | 
			
		||||
    taskGroup {
 | 
			
		||||
      id
 | 
			
		||||
      name
 | 
			
		||||
    }
 | 
			
		||||
    badges {
 | 
			
		||||
      checklist {
 | 
			
		||||
@@ -2795,6 +2839,9 @@ export const MeDocument = gql`
 | 
			
		||||
    user {
 | 
			
		||||
      id
 | 
			
		||||
      fullName
 | 
			
		||||
      username
 | 
			
		||||
      email
 | 
			
		||||
      bio
 | 
			
		||||
      profileIcon {
 | 
			
		||||
        initials
 | 
			
		||||
        bgColor
 | 
			
		||||
@@ -2998,7 +3045,7 @@ export type UpdateProjectMemberRoleMutationHookResult = ReturnType<typeof useUpd
 | 
			
		||||
export type UpdateProjectMemberRoleMutationResult = ApolloReactCommon.MutationResult<UpdateProjectMemberRoleMutation>;
 | 
			
		||||
export type UpdateProjectMemberRoleMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectMemberRoleMutation, UpdateProjectMemberRoleMutationVariables>;
 | 
			
		||||
export const CreateTaskDocument = gql`
 | 
			
		||||
    mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
 | 
			
		||||
    mutation createTask($taskGroupID: UUID!, $name: String!, $position: Float!) {
 | 
			
		||||
  createTask(input: {taskGroupID: $taskGroupID, name: $name, position: $position}) {
 | 
			
		||||
    ...TaskFields
 | 
			
		||||
  }
 | 
			
		||||
@@ -4271,6 +4318,7 @@ export const CreateUserAccountDocument = gql`
 | 
			
		||||
    fullName
 | 
			
		||||
    initials
 | 
			
		||||
    username
 | 
			
		||||
    bio
 | 
			
		||||
    profileIcon {
 | 
			
		||||
      url
 | 
			
		||||
      initials
 | 
			
		||||
@@ -4369,6 +4417,49 @@ export function useDeleteUserAccountMutation(baseOptions?: ApolloReactHooks.Muta
 | 
			
		||||
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
 | 
			
		||||
export type DeleteUserAccountMutationResult = ApolloReactCommon.MutationResult<DeleteUserAccountMutation>;
 | 
			
		||||
export type DeleteUserAccountMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
 | 
			
		||||
export const UpdateUserInfoDocument = gql`
 | 
			
		||||
    mutation updateUserInfo($name: String!, $initials: String!, $email: String!, $bio: String!) {
 | 
			
		||||
  updateUserInfo(input: {name: $name, initials: $initials, email: $email, bio: $bio}) {
 | 
			
		||||
    user {
 | 
			
		||||
      id
 | 
			
		||||
      email
 | 
			
		||||
      fullName
 | 
			
		||||
      bio
 | 
			
		||||
      profileIcon {
 | 
			
		||||
        initials
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
    `;
 | 
			
		||||
export type UpdateUserInfoMutationFn = ApolloReactCommon.MutationFunction<UpdateUserInfoMutation, UpdateUserInfoMutationVariables>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * __useUpdateUserInfoMutation__
 | 
			
		||||
 *
 | 
			
		||||
 * To run a mutation, you first call `useUpdateUserInfoMutation` within a React component and pass it any options that fit your needs.
 | 
			
		||||
 * When your component renders, `useUpdateUserInfoMutation` returns a tuple that includes:
 | 
			
		||||
 * - A mutate function that you can call at any time to execute the mutation
 | 
			
		||||
 * - An object with fields that represent the current status of the mutation's execution
 | 
			
		||||
 *
 | 
			
		||||
 * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
 | 
			
		||||
 *
 | 
			
		||||
 * @example
 | 
			
		||||
 * const [updateUserInfoMutation, { data, loading, error }] = useUpdateUserInfoMutation({
 | 
			
		||||
 *   variables: {
 | 
			
		||||
 *      name: // value for 'name'
 | 
			
		||||
 *      initials: // value for 'initials'
 | 
			
		||||
 *      email: // value for 'email'
 | 
			
		||||
 *      bio: // value for 'bio'
 | 
			
		||||
 *   },
 | 
			
		||||
 * });
 | 
			
		||||
 */
 | 
			
		||||
export function useUpdateUserInfoMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateUserInfoMutation, UpdateUserInfoMutationVariables>) {
 | 
			
		||||
        return ApolloReactHooks.useMutation<UpdateUserInfoMutation, UpdateUserInfoMutationVariables>(UpdateUserInfoDocument, baseOptions);
 | 
			
		||||
      }
 | 
			
		||||
export type UpdateUserInfoMutationHookResult = ReturnType<typeof useUpdateUserInfoMutation>;
 | 
			
		||||
export type UpdateUserInfoMutationResult = ApolloReactCommon.MutationResult<UpdateUserInfoMutation>;
 | 
			
		||||
export type UpdateUserInfoMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateUserInfoMutation, UpdateUserInfoMutationVariables>;
 | 
			
		||||
export const UpdateUserPasswordDocument = gql`
 | 
			
		||||
    mutation updateUserPassword($userID: UUID!, $password: String!) {
 | 
			
		||||
  updateUserPassword(input: {userID: $userID, password: $password}) {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ query findTask($taskID: UUID!) {
 | 
			
		||||
    complete
 | 
			
		||||
    taskGroup {
 | 
			
		||||
      id
 | 
			
		||||
      name
 | 
			
		||||
    }
 | 
			
		||||
    badges {
 | 
			
		||||
      checklist {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,9 @@ query me {
 | 
			
		||||
    user {
 | 
			
		||||
      id
 | 
			
		||||
      fullName
 | 
			
		||||
      username
 | 
			
		||||
      email
 | 
			
		||||
      bio
 | 
			
		||||
      profileIcon {
 | 
			
		||||
        initials
 | 
			
		||||
        bgColor
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import gql from 'graphql-tag';
 | 
			
		||||
import TASK_FRAGMENT from '../fragments/task';
 | 
			
		||||
 | 
			
		||||
const CREATE_TASK_MUTATION = gql`
 | 
			
		||||
  mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
 | 
			
		||||
  mutation createTask($taskGroupID: UUID!, $name: String!, $position: Float!) {
 | 
			
		||||
    createTask(input: { taskGroupID: $taskGroupID, name: $name, position: $position }) {
 | 
			
		||||
      ...TaskFields
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ export const CREATE_USER_MUTATION = gql`
 | 
			
		||||
      fullName
 | 
			
		||||
      initials
 | 
			
		||||
      username
 | 
			
		||||
      bio
 | 
			
		||||
      profileIcon {
 | 
			
		||||
        url
 | 
			
		||||
        initials
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								frontend/src/shared/graphql/user/updateUserInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/src/shared/graphql/user/updateUserInfo.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import gql from 'graphql-tag';
 | 
			
		||||
 | 
			
		||||
export const UPDATE_USER_INFO_MUTATION = gql`
 | 
			
		||||
  mutation updateUserInfo($name: String!, $initials: String!, $email: String!, $bio: String!) {
 | 
			
		||||
    updateUserInfo(input: { name: $name, initials: $initials, email: $email, bio: $bio }) {
 | 
			
		||||
      user {
 | 
			
		||||
        id
 | 
			
		||||
        email
 | 
			
		||||
        fullName
 | 
			
		||||
        bio
 | 
			
		||||
        profileIcon {
 | 
			
		||||
          initials
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default UPDATE_USER_INFO_MUTATION;
 | 
			
		||||
							
								
								
									
										13
									
								
								frontend/src/shared/utils/error.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/shared/utils/error.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { ApolloError } from '@apollo/client';
 | 
			
		||||
 | 
			
		||||
export default function hasNotFoundError(...errors: Array<ApolloError | undefined>) {
 | 
			
		||||
  for (const error of errors) {
 | 
			
		||||
    if (error && error.graphQLErrors.length !== 0) {
 | 
			
		||||
      const notFound = error.graphQLErrors.find(e => e.extensions && e.extensions.code === '404');
 | 
			
		||||
      if (notFound) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								frontend/src/taskcafe.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								frontend/src/taskcafe.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -46,6 +46,8 @@ type OwnedList = {
 | 
			
		||||
type TaskUser = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  fullName: string;
 | 
			
		||||
  email?: string;
 | 
			
		||||
  bio?: string;
 | 
			
		||||
  profileIcon: ProfileIcon;
 | 
			
		||||
  username?: string;
 | 
			
		||||
  role?: Role;
 | 
			
		||||
 
 | 
			
		||||
@@ -157,4 +157,5 @@ type UserAccount struct {
 | 
			
		||||
	Initials         string         `json:"initials"`
 | 
			
		||||
	ProfileAvatarUrl sql.NullString `json:"profile_avatar_url"`
 | 
			
		||||
	RoleCode         string         `json:"role_code"`
 | 
			
		||||
	Bio              string         `json:"bio"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -114,6 +114,7 @@ type Querier interface {
 | 
			
		||||
	UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
 | 
			
		||||
	UpdateTaskPosition(ctx context.Context, arg UpdateTaskPositionParams) (Task, error)
 | 
			
		||||
	UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
 | 
			
		||||
	UpdateUserAccountInfo(ctx context.Context, arg UpdateUserAccountInfoParams) (UserAccount, error)
 | 
			
		||||
	UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
 | 
			
		||||
	UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UserAccount, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,10 @@ INSERT INTO user_account(full_name, initials, email, username, created_at, passw
 | 
			
		||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
 | 
			
		||||
  RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: UpdateUserAccountInfo :one
 | 
			
		||||
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
 | 
			
		||||
  WHERE user_id = $1 RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: DeleteUserAccountByID :exec
 | 
			
		||||
DELETE FROM user_account WHERE user_id = $1;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import (
 | 
			
		||||
 | 
			
		||||
const createUserAccount = `-- name: CreateUserAccount :one
 | 
			
		||||
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code)
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
 | 
			
		||||
  VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type CreateUserAccountParams struct {
 | 
			
		||||
@@ -48,6 +48,7 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
@@ -62,7 +63,7 @@ func (q *Queries) DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getAllUserAccounts = `-- name: GetAllUserAccounts :many
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account WHERE username != 'system'
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE username != 'system'
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) {
 | 
			
		||||
@@ -85,6 +86,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
 | 
			
		||||
			&i.Initials,
 | 
			
		||||
			&i.ProfileAvatarUrl,
 | 
			
		||||
			&i.RoleCode,
 | 
			
		||||
			&i.Bio,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -119,7 +121,7 @@ func (q *Queries) GetRoleForUserID(ctx context.Context, userID uuid.UUID) (GetRo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserAccountByID = `-- name: GetUserAccountByID :one
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account WHERE user_id = $1
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE user_id = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) {
 | 
			
		||||
@@ -136,12 +138,13 @@ func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (Use
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getUserAccountByUsername = `-- name: GetUserAccountByUsername :one
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account WHERE username = $1
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account WHERE username = $1
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) {
 | 
			
		||||
@@ -158,12 +161,13 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setUserPassword = `-- name: SetUserPassword :one
 | 
			
		||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
 | 
			
		||||
UPDATE user_account SET password_hash = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type SetUserPasswordParams struct {
 | 
			
		||||
@@ -185,13 +189,52 @@ func (q *Queries) SetUserPassword(ctx context.Context, arg SetUserPasswordParams
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserAccountInfo = `-- name: UpdateUserAccountInfo :one
 | 
			
		||||
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
 | 
			
		||||
  WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserAccountInfoParams struct {
 | 
			
		||||
	UserID   uuid.UUID `json:"user_id"`
 | 
			
		||||
	Bio      string    `json:"bio"`
 | 
			
		||||
	FullName string    `json:"full_name"`
 | 
			
		||||
	Initials string    `json:"initials"`
 | 
			
		||||
	Email    string    `json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *Queries) UpdateUserAccountInfo(ctx context.Context, arg UpdateUserAccountInfoParams) (UserAccount, error) {
 | 
			
		||||
	row := q.db.QueryRowContext(ctx, updateUserAccountInfo,
 | 
			
		||||
		arg.UserID,
 | 
			
		||||
		arg.Bio,
 | 
			
		||||
		arg.FullName,
 | 
			
		||||
		arg.Initials,
 | 
			
		||||
		arg.Email,
 | 
			
		||||
	)
 | 
			
		||||
	var i UserAccount
 | 
			
		||||
	err := row.Scan(
 | 
			
		||||
		&i.UserID,
 | 
			
		||||
		&i.CreatedAt,
 | 
			
		||||
		&i.Email,
 | 
			
		||||
		&i.Username,
 | 
			
		||||
		&i.PasswordHash,
 | 
			
		||||
		&i.ProfileBgColor,
 | 
			
		||||
		&i.FullName,
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one
 | 
			
		||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
 | 
			
		||||
  RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
 | 
			
		||||
  RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserAccountProfileAvatarURLParams struct {
 | 
			
		||||
@@ -213,12 +256,13 @@ func (q *Queries) UpdateUserAccountProfileAvatarURL(ctx context.Context, arg Upd
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateUserRole = `-- name: UpdateUserRole :one
 | 
			
		||||
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
 | 
			
		||||
UPDATE user_account SET role_code = $2 WHERE user_id = $1 RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type UpdateUserRoleParams struct {
 | 
			
		||||
@@ -240,6 +284,7 @@ func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams)
 | 
			
		||||
		&i.Initials,
 | 
			
		||||
		&i.ProfileAvatarUrl,
 | 
			
		||||
		&i.RoleCode,
 | 
			
		||||
		&i.Bio,
 | 
			
		||||
	)
 | 
			
		||||
	return i, err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -210,6 +210,7 @@ type ComplexityRoot struct {
 | 
			
		||||
		UpdateTaskLocation              func(childComplexity int, input NewTaskLocation) int
 | 
			
		||||
		UpdateTaskName                  func(childComplexity int, input UpdateTaskName) int
 | 
			
		||||
		UpdateTeamMemberRole            func(childComplexity int, input UpdateTeamMemberRole) int
 | 
			
		||||
		UpdateUserInfo                  func(childComplexity int, input UpdateUserInfo) int
 | 
			
		||||
		UpdateUserPassword              func(childComplexity int, input UpdateUserPassword) int
 | 
			
		||||
		UpdateUserRole                  func(childComplexity int, input UpdateUserRole) int
 | 
			
		||||
	}
 | 
			
		||||
@@ -404,6 +405,10 @@ type ComplexityRoot struct {
 | 
			
		||||
		TeamID func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	UpdateUserInfoPayload struct {
 | 
			
		||||
		User func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	UpdateUserPasswordPayload struct {
 | 
			
		||||
		Ok   func(childComplexity int) int
 | 
			
		||||
		User func(childComplexity int) int
 | 
			
		||||
@@ -414,6 +419,7 @@ type ComplexityRoot struct {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	UserAccount struct {
 | 
			
		||||
		Bio         func(childComplexity int) int
 | 
			
		||||
		CreatedAt   func(childComplexity int) int
 | 
			
		||||
		Email       func(childComplexity int) int
 | 
			
		||||
		FullName    func(childComplexity int) int
 | 
			
		||||
@@ -482,6 +488,7 @@ type MutationResolver interface {
 | 
			
		||||
	ClearProfileAvatar(ctx context.Context) (*db.UserAccount, error)
 | 
			
		||||
	UpdateUserPassword(ctx context.Context, input UpdateUserPassword) (*UpdateUserPasswordPayload, error)
 | 
			
		||||
	UpdateUserRole(ctx context.Context, input UpdateUserRole) (*UpdateUserRolePayload, error)
 | 
			
		||||
	UpdateUserInfo(ctx context.Context, input UpdateUserInfo) (*UpdateUserInfoPayload, error)
 | 
			
		||||
}
 | 
			
		||||
type NotificationResolver interface {
 | 
			
		||||
	ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error)
 | 
			
		||||
@@ -1493,6 +1500,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Mutation.UpdateTeamMemberRole(childComplexity, args["input"].(UpdateTeamMemberRole)), true
 | 
			
		||||
 | 
			
		||||
	case "Mutation.updateUserInfo":
 | 
			
		||||
		if e.complexity.Mutation.UpdateUserInfo == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		args, err := ec.field_Mutation_updateUserInfo_args(context.TODO(), rawArgs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Mutation.UpdateUserInfo(childComplexity, args["input"].(UpdateUserInfo)), true
 | 
			
		||||
 | 
			
		||||
	case "Mutation.updateUserPassword":
 | 
			
		||||
		if e.complexity.Mutation.UpdateUserPassword == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2284,6 +2303,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.UpdateTeamMemberRolePayload.TeamID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "UpdateUserInfoPayload.user":
 | 
			
		||||
		if e.complexity.UpdateUserInfoPayload.User == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.UpdateUserInfoPayload.User(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "UpdateUserPasswordPayload.ok":
 | 
			
		||||
		if e.complexity.UpdateUserPasswordPayload.Ok == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2305,6 +2331,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.UpdateUserRolePayload.User(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "UserAccount.bio":
 | 
			
		||||
		if e.complexity.UserAccount.Bio == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.UserAccount.Bio(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "UserAccount.createdAt":
 | 
			
		||||
		if e.complexity.UserAccount.CreatedAt == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -2519,6 +2552,7 @@ type UserAccount {
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
  fullName: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
  role: Role!
 | 
			
		||||
  username: String!
 | 
			
		||||
  profileIcon: ProfileIcon!
 | 
			
		||||
@@ -2614,6 +2648,7 @@ enum ObjectType {
 | 
			
		||||
  TEAM
 | 
			
		||||
  PROJECT
 | 
			
		||||
  TASK
 | 
			
		||||
  TASK_GROUP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
 | 
			
		||||
@@ -2817,20 +2852,20 @@ type UpdateProjectMemberRolePayload {
 | 
			
		||||
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTask(input: NewTask!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK_GROUP)
 | 
			
		||||
  deleteTask(input: DeleteTaskInput!):
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  updateTaskDescription(input: UpdateTaskDescriptionInput!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskLocation(input: NewTaskLocation!):
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskName(input: UpdateTaskName!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  setTaskComplete(input: SetTaskComplete!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskDueDate(input: UpdateTaskDueDate!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  assignTask(input: AssignTaskInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
@@ -2839,7 +2874,7 @@ extend type Mutation {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTask {
 | 
			
		||||
  taskGroupID: String!
 | 
			
		||||
  taskGroupID: UUID!
 | 
			
		||||
  name: String!
 | 
			
		||||
  position: Float!
 | 
			
		||||
}
 | 
			
		||||
@@ -3163,6 +3198,19 @@ extend type Mutation {
 | 
			
		||||
  updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
 | 
			
		||||
  updateUserRole(input: UpdateUserRole!):
 | 
			
		||||
   UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
  updateUserInfo(input: UpdateUserInfo!):
 | 
			
		||||
    UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserInfoPayload {
 | 
			
		||||
  user: UserAccount!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserInfo {
 | 
			
		||||
  name: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  email: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserPassword {
 | 
			
		||||
@@ -3921,6 +3969,20 @@ func (ec *executionContext) field_Mutation_updateTeamMemberRole_args(ctx context
 | 
			
		||||
	return args, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) field_Mutation_updateUserInfo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	args := map[string]interface{}{}
 | 
			
		||||
	var arg0 UpdateUserInfo
 | 
			
		||||
	if tmp, ok := rawArgs["input"]; ok {
 | 
			
		||||
		arg0, err = ec.unmarshalNUpdateUserInfo2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserInfo(ctx, tmp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	args["input"] = arg0
 | 
			
		||||
	return args, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) field_Mutation_updateUserPassword_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	args := map[string]interface{}{}
 | 
			
		||||
@@ -6472,7 +6534,7 @@ func (ec *executionContext) _Mutation_createTask(ctx context.Context, field grap
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK_GROUP")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6545,7 +6607,7 @@ func (ec *executionContext) _Mutation_deleteTask(ctx context.Context, field grap
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6618,7 +6680,7 @@ func (ec *executionContext) _Mutation_updateTaskDescription(ctx context.Context,
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6691,7 +6753,7 @@ func (ec *executionContext) _Mutation_updateTaskLocation(ctx context.Context, fi
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6764,7 +6826,7 @@ func (ec *executionContext) _Mutation_updateTaskName(ctx context.Context, field
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6837,7 +6899,7 @@ func (ec *executionContext) _Mutation_setTaskComplete(ctx context.Context, field
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -6910,7 +6972,7 @@ func (ec *executionContext) _Mutation_updateTaskDueDate(ctx context.Context, fie
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "PROJECT")
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "TASK")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -9221,6 +9283,79 @@ func (ec *executionContext) _Mutation_updateUserRole(ctx context.Context, field
 | 
			
		||||
	return ec.marshalNUpdateUserRolePayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserRolePayload(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Mutation_updateUserInfo(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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	rawArgs := field.ArgumentMap(ec.Variables)
 | 
			
		||||
	args, err := ec.field_Mutation_updateUserInfo_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().UpdateUserInfo(rctx, args["input"].(UpdateUserInfo))
 | 
			
		||||
		}
 | 
			
		||||
		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, "ORG")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			typeArg, err := ec.unmarshalNObjectType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐObjectType(ctx, "ORG")
 | 
			
		||||
			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, err
 | 
			
		||||
		}
 | 
			
		||||
		if tmp == nil {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		if data, ok := tmp.(*UpdateUserInfoPayload); ok {
 | 
			
		||||
			return data, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.UpdateUserInfoPayload`, 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.(*UpdateUserInfoPayload)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNUpdateUserInfoPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserInfoPayload(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Notification_id(ctx context.Context, field graphql.CollectedField, obj *db.Notification) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -12905,6 +13040,40 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload_member(ctx context.Cont
 | 
			
		||||
	return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMember(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UpdateUserInfoPayload_user(ctx context.Context, field graphql.CollectedField, obj *UpdateUserInfoPayload) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "UpdateUserInfoPayload",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 | 
			
		||||
		ctx = rctx // use context from middleware stack in children
 | 
			
		||||
		return obj.User, 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.UserAccount)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UpdateUserPasswordPayload_ok(ctx context.Context, field graphql.CollectedField, obj *UpdateUserPasswordPayload) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -13177,6 +13346,40 @@ func (ec *executionContext) _UserAccount_initials(ctx context.Context, field gra
 | 
			
		||||
	return ec.marshalNString2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UserAccount_bio(ctx context.Context, field graphql.CollectedField, obj *db.UserAccount) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "UserAccount",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: 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.Bio, 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.(string)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNString2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UserAccount_role(ctx context.Context, field graphql.CollectedField, obj *db.UserAccount) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -14992,7 +15195,7 @@ func (ec *executionContext) unmarshalInputNewTask(ctx context.Context, obj inter
 | 
			
		||||
		switch k {
 | 
			
		||||
		case "taskGroupID":
 | 
			
		||||
			var err error
 | 
			
		||||
			it.TaskGroupID, err = ec.unmarshalNString2string(ctx, v)
 | 
			
		||||
			it.TaskGroupID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -15710,6 +15913,42 @@ func (ec *executionContext) unmarshalInputUpdateTeamMemberRole(ctx context.Conte
 | 
			
		||||
	return it, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalInputUpdateUserInfo(ctx context.Context, obj interface{}) (UpdateUserInfo, error) {
 | 
			
		||||
	var it UpdateUserInfo
 | 
			
		||||
	var asMap = obj.(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	for k, v := range asMap {
 | 
			
		||||
		switch k {
 | 
			
		||||
		case "name":
 | 
			
		||||
			var err error
 | 
			
		||||
			it.Name, err = ec.unmarshalNString2string(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "initials":
 | 
			
		||||
			var err error
 | 
			
		||||
			it.Initials, err = ec.unmarshalNString2string(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "email":
 | 
			
		||||
			var err error
 | 
			
		||||
			it.Email, err = ec.unmarshalNString2string(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "bio":
 | 
			
		||||
			var err error
 | 
			
		||||
			it.Bio, err = ec.unmarshalNString2string(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return it, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalInputUpdateUserPassword(ctx context.Context, obj interface{}) (UpdateUserPassword, error) {
 | 
			
		||||
	var it UpdateUserPassword
 | 
			
		||||
	var asMap = obj.(map[string]interface{})
 | 
			
		||||
@@ -16671,6 +16910,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "updateUserInfo":
 | 
			
		||||
			out.Values[i] = ec._Mutation_updateUserInfo(ctx, field)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
@@ -18235,6 +18479,33 @@ func (ec *executionContext) _UpdateTeamMemberRolePayload(ctx context.Context, se
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var updateUserInfoPayloadImplementors = []string{"UpdateUserInfoPayload"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UpdateUserInfoPayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserInfoPayload) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, updateUserInfoPayloadImplementors)
 | 
			
		||||
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("UpdateUserInfoPayload")
 | 
			
		||||
		case "user":
 | 
			
		||||
			out.Values[i] = ec._UpdateUserInfoPayload_user(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 updateUserPasswordPayloadImplementors = []string{"UpdateUserPasswordPayload"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _UpdateUserPasswordPayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateUserPasswordPayload) graphql.Marshaler {
 | 
			
		||||
@@ -18339,6 +18610,11 @@ func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionS
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				atomic.AddUint32(&invalids, 1)
 | 
			
		||||
			}
 | 
			
		||||
		case "bio":
 | 
			
		||||
			out.Values[i] = ec._UserAccount_bio(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				atomic.AddUint32(&invalids, 1)
 | 
			
		||||
			}
 | 
			
		||||
		case "role":
 | 
			
		||||
			field := field
 | 
			
		||||
			out.Concurrently(i, func() (res graphql.Marshaler) {
 | 
			
		||||
@@ -20203,6 +20479,24 @@ func (ec *executionContext) marshalNUpdateTeamMemberRolePayload2ᚖgithubᚗcom
 | 
			
		||||
	return ec._UpdateTeamMemberRolePayload(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNUpdateUserInfo2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserInfo(ctx context.Context, v interface{}) (UpdateUserInfo, error) {
 | 
			
		||||
	return ec.unmarshalInputUpdateUserInfo(ctx, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNUpdateUserInfoPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserInfoPayload(ctx context.Context, sel ast.SelectionSet, v UpdateUserInfoPayload) graphql.Marshaler {
 | 
			
		||||
	return ec._UpdateUserInfoPayload(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNUpdateUserInfoPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserInfoPayload(ctx context.Context, sel ast.SelectionSet, v *UpdateUserInfoPayload) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
 | 
			
		||||
			ec.Errorf(ctx, "must not be null")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return ec._UpdateUserInfoPayload(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNUpdateUserPassword2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐUpdateUserPassword(ctx context.Context, v interface{}) (UpdateUserPassword, error) {
 | 
			
		||||
	return ec.unmarshalInputUpdateUserPassword(ctx, v)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/db"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/utils"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/vektah/gqlparser/v2/gqlerror"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewHandler returns a new graphql endpoint handler.
 | 
			
		||||
@@ -51,6 +52,8 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
			fieldName = "TeamID"
 | 
			
		||||
		case ObjectTypeTask:
 | 
			
		||||
			fieldName = "TaskID"
 | 
			
		||||
		case ObjectTypeTaskGroup:
 | 
			
		||||
			fieldName = "TaskGroupID"
 | 
			
		||||
		default:
 | 
			
		||||
			fieldName = "ProjectID"
 | 
			
		||||
		}
 | 
			
		||||
@@ -68,6 +71,13 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
			} else if typeArg == ObjectTypeTaskGroup {
 | 
			
		||||
				log.WithFields(log.Fields{"subjectID": subjectID}).Info("fetching project ID using task group ID")
 | 
			
		||||
				taskGroup, err := repo.GetTaskGroupByID(ctx, subjectID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
				subjectID = taskGroup.ProjectID
 | 
			
		||||
			}
 | 
			
		||||
			roles, err := GetProjectRoles(ctx, repo, subjectID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -186,3 +196,13 @@ func GetActionType(actionType int32) ActionType {
 | 
			
		||||
		panic("Not a valid entity type!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotFoundError creates a 404 gqlerror
 | 
			
		||||
func NotFoundError(message string) error {
 | 
			
		||||
	return &gqlerror.Error{
 | 
			
		||||
		Message: message,
 | 
			
		||||
		Extensions: map[string]interface{}{
 | 
			
		||||
			"code": "404",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -229,9 +229,9 @@ type NewRefreshToken struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NewTask struct {
 | 
			
		||||
	TaskGroupID string  `json:"taskGroupID"`
 | 
			
		||||
	Name        string  `json:"name"`
 | 
			
		||||
	Position    float64 `json:"position"`
 | 
			
		||||
	TaskGroupID uuid.UUID `json:"taskGroupID"`
 | 
			
		||||
	Name        string    `json:"name"`
 | 
			
		||||
	Position    float64   `json:"position"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NewTaskGroup struct {
 | 
			
		||||
@@ -455,6 +455,17 @@ type UpdateTeamMemberRolePayload struct {
 | 
			
		||||
	Member *Member   `json:"member"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserInfo struct {
 | 
			
		||||
	Name     string `json:"name"`
 | 
			
		||||
	Initials string `json:"initials"`
 | 
			
		||||
	Email    string `json:"email"`
 | 
			
		||||
	Bio      string `json:"bio"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserInfoPayload struct {
 | 
			
		||||
	User *db.UserAccount `json:"user"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserPassword struct {
 | 
			
		||||
	UserID   uuid.UUID `json:"userID"`
 | 
			
		||||
	Password string    `json:"password"`
 | 
			
		||||
@@ -637,10 +648,11 @@ func (e EntityType) MarshalGQL(w io.Writer) {
 | 
			
		||||
type ObjectType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ObjectTypeOrg     ObjectType = "ORG"
 | 
			
		||||
	ObjectTypeTeam    ObjectType = "TEAM"
 | 
			
		||||
	ObjectTypeProject ObjectType = "PROJECT"
 | 
			
		||||
	ObjectTypeTask    ObjectType = "TASK"
 | 
			
		||||
	ObjectTypeOrg       ObjectType = "ORG"
 | 
			
		||||
	ObjectTypeTeam      ObjectType = "TEAM"
 | 
			
		||||
	ObjectTypeProject   ObjectType = "PROJECT"
 | 
			
		||||
	ObjectTypeTask      ObjectType = "TASK"
 | 
			
		||||
	ObjectTypeTaskGroup ObjectType = "TASK_GROUP"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var AllObjectType = []ObjectType{
 | 
			
		||||
@@ -648,11 +660,12 @@ var AllObjectType = []ObjectType{
 | 
			
		||||
	ObjectTypeTeam,
 | 
			
		||||
	ObjectTypeProject,
 | 
			
		||||
	ObjectTypeTask,
 | 
			
		||||
	ObjectTypeTaskGroup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e ObjectType) IsValid() bool {
 | 
			
		||||
	switch e {
 | 
			
		||||
	case ObjectTypeOrg, ObjectTypeTeam, ObjectTypeProject, ObjectTypeTask:
 | 
			
		||||
	case ObjectTypeOrg, ObjectTypeTeam, ObjectTypeProject, ObjectTypeTask, ObjectTypeTaskGroup:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ type UserAccount {
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
  fullName: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
  role: Role!
 | 
			
		||||
  username: String!
 | 
			
		||||
  profileIcon: ProfileIcon!
 | 
			
		||||
@@ -173,6 +174,7 @@ enum ObjectType {
 | 
			
		||||
  TEAM
 | 
			
		||||
  PROJECT
 | 
			
		||||
  TASK
 | 
			
		||||
  TASK_GROUP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
 | 
			
		||||
@@ -376,20 +378,20 @@ type UpdateProjectMemberRolePayload {
 | 
			
		||||
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTask(input: NewTask!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK_GROUP)
 | 
			
		||||
  deleteTask(input: DeleteTaskInput!):
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  updateTaskDescription(input: UpdateTaskDescriptionInput!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskLocation(input: NewTaskLocation!):
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskName(input: UpdateTaskName!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  setTaskComplete(input: SetTaskComplete!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskDueDate(input: UpdateTaskDueDate!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  assignTask(input: AssignTaskInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
@@ -398,7 +400,7 @@ extend type Mutation {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTask {
 | 
			
		||||
  taskGroupID: String!
 | 
			
		||||
  taskGroupID: UUID!
 | 
			
		||||
  name: String!
 | 
			
		||||
  position: Float!
 | 
			
		||||
}
 | 
			
		||||
@@ -722,6 +724,19 @@ extend type Mutation {
 | 
			
		||||
  updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
 | 
			
		||||
  updateUserRole(input: UpdateUserRole!):
 | 
			
		||||
   UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
  updateUserInfo(input: UpdateUserInfo!):
 | 
			
		||||
    UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserInfoPayload {
 | 
			
		||||
  user: UserAccount!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserInfo {
 | 
			
		||||
  name: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  email: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserPassword {
 | 
			
		||||
 
 | 
			
		||||
@@ -179,14 +179,9 @@ func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input Up
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.Task, error) {
 | 
			
		||||
	taskGroupID, err := uuid.Parse(input.TaskGroupID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("issue while parsing task group ID")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
	}
 | 
			
		||||
	createdAt := time.Now().UTC()
 | 
			
		||||
	log.WithFields(log.Fields{"positon": input.Position, "taskGroupID": taskGroupID}).Info("creating task")
 | 
			
		||||
	task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{taskGroupID, createdAt, input.Name, input.Position})
 | 
			
		||||
	log.WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task")
 | 
			
		||||
	task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("issue while creating task")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
@@ -238,6 +233,9 @@ func (r *mutationResolver) SetTaskComplete(ctx context.Context, input SetTaskCom
 | 
			
		||||
	completedAt := time.Now().UTC()
 | 
			
		||||
	task, err := r.Repository.SetTaskComplete(ctx, db.SetTaskCompleteParams{TaskID: input.TaskID, Complete: input.Complete, CompletedAt: sql.NullTime{Time: completedAt, Valid: true}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == sql.ErrNoRows {
 | 
			
		||||
			return &db.Task{}, NotFoundError("task does not exist")
 | 
			
		||||
		}
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return &task, nil
 | 
			
		||||
@@ -826,6 +824,17 @@ func (r *mutationResolver) UpdateUserRole(ctx context.Context, input UpdateUserR
 | 
			
		||||
	return &UpdateUserRolePayload{User: &user}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) UpdateUserInfo(ctx context.Context, input UpdateUserInfo) (*UpdateUserInfoPayload, error) {
 | 
			
		||||
	userID, ok := GetUserID(ctx)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return &UpdateUserInfoPayload{}, errors.New("invalid user ID")
 | 
			
		||||
	}
 | 
			
		||||
	user, err := r.Repository.UpdateUserAccountInfo(ctx, db.UpdateUserAccountInfoParams{
 | 
			
		||||
		Bio: input.Bio, FullName: input.Name, Initials: input.Initials, Email: input.Email, UserID: userID,
 | 
			
		||||
	})
 | 
			
		||||
	return &UpdateUserInfoPayload{User: &user}, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uuid.UUID, error) {
 | 
			
		||||
	return obj.NotificationID, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1022,6 +1031,14 @@ func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db
 | 
			
		||||
 | 
			
		||||
func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) {
 | 
			
		||||
	task, err := r.Repository.GetTaskByID(ctx, input.TaskID)
 | 
			
		||||
	if err == sql.ErrNoRows {
 | 
			
		||||
		return &db.Task{}, &gqlerror.Error{
 | 
			
		||||
			Message: "Task does not exist",
 | 
			
		||||
			Extensions: map[string]interface{}{
 | 
			
		||||
				"code": "404",
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &task, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1229,6 +1246,9 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *db.Task) ([]Member, er
 | 
			
		||||
	taskMemberLinks, err := r.Repository.GetAssignedMembersForTask(ctx, obj.TaskID)
 | 
			
		||||
	taskMembers := []Member{}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == sql.ErrNoRows {
 | 
			
		||||
			return taskMembers, nil
 | 
			
		||||
		}
 | 
			
		||||
		return taskMembers, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, taskMemberLink := range taskMemberLinks {
 | 
			
		||||
@@ -1263,11 +1283,19 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *db.Task) ([]Member, er
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskResolver) Labels(ctx context.Context, obj *db.Task) ([]db.TaskLabel, error) {
 | 
			
		||||
	return r.Repository.GetTaskLabelsForTaskID(ctx, obj.TaskID)
 | 
			
		||||
	labels, err := r.Repository.GetTaskLabelsForTaskID(ctx, obj.TaskID)
 | 
			
		||||
	if err != nil && err != sql.ErrNoRows {
 | 
			
		||||
		return []db.TaskLabel{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return labels, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskResolver) Checklists(ctx context.Context, obj *db.Task) ([]db.TaskChecklist, error) {
 | 
			
		||||
	return r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID)
 | 
			
		||||
	checklists, err := r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID)
 | 
			
		||||
	if err != nil && err != sql.ErrNoRows {
 | 
			
		||||
		return []db.TaskChecklist{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return checklists, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ type UserAccount {
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
  fullName: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
  role: Role!
 | 
			
		||||
  username: String!
 | 
			
		||||
  profileIcon: ProfileIcon!
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ enum ObjectType {
 | 
			
		||||
  TEAM
 | 
			
		||||
  PROJECT
 | 
			
		||||
  TASK
 | 
			
		||||
  TASK_GROUP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTask(input: NewTask!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK_GROUP)
 | 
			
		||||
  deleteTask(input: DeleteTaskInput!):
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  updateTaskDescription(input: UpdateTaskDescriptionInput!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskLocation(input: NewTaskLocation!):
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskName(input: UpdateTaskName!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  setTaskComplete(input: SetTaskComplete!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
  updateTaskDueDate(input: UpdateTaskDueDate!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
 | 
			
		||||
  assignTask(input: AssignTaskInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: TASK)
 | 
			
		||||
@@ -22,7 +22,7 @@ extend type Mutation {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTask {
 | 
			
		||||
  taskGroupID: String!
 | 
			
		||||
  taskGroupID: UUID!
 | 
			
		||||
  name: String!
 | 
			
		||||
  position: Float!
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,19 @@ extend type Mutation {
 | 
			
		||||
  updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
 | 
			
		||||
  updateUserRole(input: UpdateUserRole!):
 | 
			
		||||
   UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
  updateUserInfo(input: UpdateUserInfo!):
 | 
			
		||||
    UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateUserInfoPayload {
 | 
			
		||||
  user: UserAccount!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserInfo {
 | 
			
		||||
  name: String!
 | 
			
		||||
  initials: String!
 | 
			
		||||
  email: String!
 | 
			
		||||
  bio: String!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserPassword {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/db"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/frontend"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Frontend serves the index.html file
 | 
			
		||||
@@ -30,7 +31,7 @@ func (h *TaskcafeHandler) Frontend(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
// ProfileImageUpload handles a user uploading a new avatar profile image
 | 
			
		||||
func (h *TaskcafeHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	log.Info("preparing to upload file")
 | 
			
		||||
	userID, ok := r.Context().Value("userID").(uuid.UUID)
 | 
			
		||||
	userID, ok := r.Context().Value(utils.UserIDKey).(uuid.UUID)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		log.Error("not a valid uuid")
 | 
			
		||||
		w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								migrations/0052_add-bio-col-to-user_account.up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/0052_add-bio-col-to-user_account.up.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
ALTER TABLE user_account ADD COLUMN bio text NOT NULL DEFAULT '';
 | 
			
		||||
		Reference in New Issue
	
	Block a user