diff --git a/client/src/components/CreateButtons.tsx b/client/src/components/CreateButtons.tsx
new file mode 100644
index 0000000..2e551e7
--- /dev/null
+++ b/client/src/components/CreateButtons.tsx
@@ -0,0 +1,87 @@
+import { useState } from 'react';
+import { Button, Box } from '@mui/material';
+import { CreateDialog } from './CreateDialog';
+import { useTaskData } from '../hooks/useTaskData';
+
+interface BaseCreateData {
+ name: string;
+}
+
+interface CreateTaskData extends BaseCreateData {
+ groupId: string;
+}
+
+interface CreateStepData extends BaseCreateData {
+ instructions: string;
+ taskId: string;
+ order: number;
+}
+
+type CreateData = BaseCreateData | CreateTaskData | CreateStepData;
+
+interface CreateButtonsProps {
+ type: 'group' | 'task' | 'step';
+ parentId?: string;
+}
+
+export function CreateButtons({ type, parentId }: CreateButtonsProps) {
+ const [open, setOpen] = useState(false);
+ const { handleCreateGroup, handleCreateTask, handleCreateStep } = useTaskData();
+
+ const handleSubmit = (data: CreateData) => {
+ let stepData: CreateStepData;
+ switch (type) {
+ case 'group':
+ handleCreateGroup(data.name);
+ break;
+ case 'task':
+ if (!parentId) {
+ console.error('Cannot create task: no group selected');
+ return;
+ }
+ handleCreateTask(data.name, parentId);
+ break;
+ case 'step':
+ if (!parentId) {
+ console.error('Cannot create step: no task selected');
+ return;
+ }
+ stepData = data as CreateStepData;
+ handleCreateStep(stepData.name, stepData.instructions, parentId, stepData.order);
+ break;
+ }
+ };
+
+ const getTitle = () => {
+ switch (type) {
+ case 'group':
+ return 'Create New Group';
+ case 'task':
+ return 'Create New Task';
+ case 'step':
+ return 'Create New Step';
+ }
+ };
+
+ const isDisabled = (type === 'task' || type === 'step') && !parentId;
+
+ return (
+
+
+ setOpen(false)}
+ onSubmit={handleSubmit}
+ title={getTitle()}
+ type={type}
+ parentId={parentId}
+ />
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/CreateDialog.tsx b/client/src/components/CreateDialog.tsx
new file mode 100644
index 0000000..8c50896
--- /dev/null
+++ b/client/src/components/CreateDialog.tsx
@@ -0,0 +1,116 @@
+import { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Box,
+} from '@mui/material';
+
+interface BaseCreateData {
+ name: string;
+}
+
+interface CreateTaskData extends BaseCreateData {
+ groupId: string;
+}
+
+interface CreateStepData extends BaseCreateData {
+ instructions: string;
+ taskId: string;
+ order: number;
+}
+
+type CreateData = BaseCreateData | CreateTaskData | CreateStepData;
+
+interface CreateDialogProps {
+ open: boolean;
+ onClose: () => void;
+ onSubmit: (data: CreateData) => void;
+ title: string;
+ type: 'group' | 'task' | 'step';
+ parentId?: string;
+}
+
+export function CreateDialog({ open, onClose, onSubmit, title, type, parentId }: CreateDialogProps) {
+ const [name, setName] = useState('');
+ const [instructions, setInstructions] = useState('');
+ const [order, setOrder] = useState(1);
+
+ const handleSubmit = () => {
+ if (type === 'task' && !parentId) {
+ console.error('Cannot create task: no group selected');
+ return;
+ }
+ if (type === 'step' && !parentId) {
+ console.error('Cannot create step: no task selected');
+ return;
+ }
+
+ const data: CreateData = {
+ name,
+ ...(type === 'task' && { groupId: parentId }),
+ ...(type === 'step' && {
+ instructions,
+ taskId: parentId,
+ order,
+ }),
+ };
+ onSubmit(data);
+ handleClose();
+ };
+
+ const handleClose = () => {
+ setName('');
+ setInstructions('');
+ setOrder(1);
+ onClose();
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/graphql/mutations.ts b/client/src/graphql/mutations.ts
index 5b82c24..b30b96d 100644
--- a/client/src/graphql/mutations.ts
+++ b/client/src/graphql/mutations.ts
@@ -1,35 +1,71 @@
import { gql } from '@apollo/client';
-export const PRINT_TASK = gql`
- mutation PrintTask($id: ID!, $userId: ID!) {
- printTask(id: $id, userId: $userId) {
+export const CREATE_GROUP = gql`
+ mutation CreateGroup($name: String!, $parentId: ID) {
+ createGroup(name: $name, parentId: $parentId) {
id
- printCount
- lastPrintedAt
+ name
+ parent_id
+ created_at
+ updated_at
+ }
+ }
+`;
+
+export const CREATE_TASK = gql`
+ mutation CreateTask($name: String!, $groupId: ID!) {
+ createTask(name: $name, groupId: $groupId) {
+ id
+ name
+ group_id
+ print_count
+ created_at
+ updated_at
+ }
+ }
+`;
+
+export const CREATE_STEP = gql`
+ mutation CreateStep($name: String!, $instructions: String!, $taskId: ID!, $order: Int!) {
+ createStep(name: $name, instructions: $instructions, taskId: $taskId, order: $order) {
+ id
+ name
+ instructions
+ task_id
+ order
+ created_at
+ updated_at
+ }
+ }
+`;
+
+export const PRINT_TASK = gql`
+ mutation PrintTask($id: ID!) {
+ printTask(id: $id) {
+ id
+ print_count
}
}
`;
export const PRINT_STEP = gql`
- mutation PrintStep($id: ID!, $userId: ID!) {
- printStep(id: $id, userId: $userId) {
+ mutation PrintStep($id: ID!) {
+ printStep(id: $id) {
id
- printCount
- lastPrintedAt
+ print_count
}
}
`;
export const CREATE_NOTE = gql`
- mutation CreateNote($content: String!, $stepId: ID!, $userId: ID!) {
- createNote(content: $content, stepId: $stepId, userId: $userId) {
+ mutation CreateNote($content: String!, $stepId: ID!) {
+ createNote(content: $content, stepId: $stepId) {
id
content
- createdAt
- user {
- id
- name
- }
+ step_id
+ user_id
+ created_at
+ updated_at
}
}
`;
\ No newline at end of file
diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts
index 89f98b1..36dd7a9 100644
--- a/client/src/graphql/queries.ts
+++ b/client/src/graphql/queries.ts
@@ -8,15 +8,15 @@ export const GET_GROUPS = gql`
tasks {
id
name
- printCount
- lastPrintedAt
+ print_count
+ last_printed_at
steps {
id
name
instructions
order
- printCount
- lastPrintedAt
+ print_count
+ last_printed_at
}
}
}
@@ -28,19 +28,19 @@ export const GET_TASKS = gql`
tasks(groupId: $groupId) {
id
name
- printCount
- lastPrintedAt
+ print_count
+ last_printed_at
steps {
id
name
instructions
order
- printCount
- lastPrintedAt
+ print_count
+ last_printed_at
notes {
id
content
- createdAt
+ created_at
user {
id
name
@@ -58,12 +58,12 @@ export const GET_STEP = gql`
name
instructions
order
- printCount
- lastPrintedAt
+ print_count
+ last_printed_at
notes {
id
content
- createdAt
+ created_at
user {
id
name
diff --git a/client/src/hooks/useTaskData.ts b/client/src/hooks/useTaskData.ts
index c45776a..2a617e0 100644
--- a/client/src/hooks/useTaskData.ts
+++ b/client/src/hooks/useTaskData.ts
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useQuery, useMutation } from '@apollo/client';
import { GET_GROUPS, GET_TASKS, GET_STEP } from '../graphql/queries';
-import { PRINT_TASK, PRINT_STEP, CREATE_NOTE } from '../graphql/mutations';
+import { PRINT_TASK, PRINT_STEP, CREATE_NOTE, CREATE_GROUP, CREATE_TASK, CREATE_STEP } from '../graphql/mutations';
import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types';
+import { doesExist, isArray } from '../utils/typeGuards';
interface GraphQLStep {
id: number;
@@ -11,19 +12,18 @@ interface GraphQLStep {
taskId: number;
order: number;
notes: GraphQLNote[];
- printCount: number;
- lastPrintedAt: string | null;
+ print_count: number;
+ last_printed_at: string | null;
}
interface GraphQLNote {
id: number;
content: string;
- createdBy: number;
- user?: {
+ created_at: string;
+ updated_at: string;
+ user: {
id: number;
name: string;
- createdAt: Date;
- updatedAt: Date;
};
}
@@ -33,62 +33,107 @@ interface GraphQLTask {
groupId: number;
steps: GraphQLStep[];
notes: GraphQLNote[];
- printCount: number;
- lastPrintedAt: string | null;
+ print_count: number;
+ last_printed_at: string | null;
}
interface GraphQLGroup {
id: number;
name: string;
tasks: GraphQLTask[];
+ created_at: string;
+ updated_at: string;
}
-function toStepWithNotes(step: GraphQLStep): StepWithNotes {
+function toStepWithNotes(step: GraphQLStep | null | undefined): StepWithNotes | null {
+ if (!doesExist(step)) return null;
+
return {
- ...step,
- notes: (step.notes || []).map((note: GraphQLNote) => ({ ...note })),
- printCount: step.printCount ?? 0,
- lastPrintedAt: step.lastPrintedAt ?? null,
+ id: step.id,
+ name: step.name,
+ instructions: step.instructions,
+ task_id: step.taskId,
+ order: step.order,
+ print_count: step.print_count,
+ last_printed_at: step.last_printed_at ? new Date(step.last_printed_at) : undefined,
+ notes: isArray(step.notes) ? step.notes.map(note => ({
+ id: note.id,
+ content: note.content,
+ created_at: new Date(note.created_at),
+ updated_at: new Date(note.updated_at),
+ created_by: note.user.id,
+ user: {
+ id: note.user.id,
+ name: note.user.name,
+ },
+ })) : [],
};
}
-function toTaskWithSteps(task: GraphQLTask): TaskWithSteps {
+function toTaskWithSteps(task: GraphQLTask | null | undefined): TaskWithSteps | null {
+ if (!doesExist(task)) return null;
+
return {
- ...task,
- steps: (task.steps || []).map(toStepWithNotes),
- notes: (task.notes || []),
- printCount: task.printCount ?? 0,
- lastPrintedAt: task.lastPrintedAt ?? null,
+ id: task.id,
+ name: task.name,
+ group_id: task.groupId,
+ print_count: task.print_count,
+ last_printed_at: task.last_printed_at ? new Date(task.last_printed_at) : undefined,
+ steps: isArray(task.steps) ? task.steps.map(step => toStepWithNotes(step)).filter(doesExist) : [],
+ notes: isArray(task.notes) ? task.notes.map(note => ({
+ id: note.id,
+ content: note.content,
+ created_at: new Date(note.created_at),
+ updated_at: new Date(note.updated_at),
+ created_by: note.user.id,
+ user: {
+ id: note.user.id,
+ name: note.user.name,
+ },
+ })) : [],
};
}
-function toGroupWithTasks(group: GraphQLGroup): GroupWithTasks {
+function toGroupWithTasks(group: GraphQLGroup | null | undefined): GroupWithTasks | null {
+ if (!doesExist(group)) return null;
+
return {
- ...group,
- tasks: (group.tasks || []).map(toTaskWithSteps),
+ id: group.id,
+ name: group.name,
+ created_at: new Date(group.created_at),
+ updated_at: new Date(group.updated_at),
+ tasks: isArray(group.tasks) ? group.tasks.map(task => toTaskWithSteps(task)).filter(doesExist) : [],
};
}
export function useTaskData() {
- const [selectedGroup, setSelectedGroup] = useState();
- const [selectedTask, setSelectedTask] = useState();
- const [selectedStep, setSelectedStep] = useState();
+ const [selectedGroup, setSelectedGroup] = useState();
+ const [selectedTask, setSelectedTask] = useState();
+ const [selectedStep, setSelectedStep] = useState();
- const { data: groupsData, loading: groupsLoading } = useQuery(GET_GROUPS);
- const { data: tasksData, loading: tasksLoading } = useQuery(GET_TASKS, {
+ const { data: groupsData, loading: groupsLoading, refetch: refetchGroups } = useQuery(GET_GROUPS);
+ const { data: tasksData, loading: tasksLoading, refetch: refetchTasks } = useQuery(GET_TASKS, {
variables: { groupId: selectedGroup?.id },
- skip: !selectedGroup,
+ skip: !selectedGroup?.id,
});
- const { data: stepData, loading: stepLoading } = useQuery(GET_STEP, {
+ const { data: stepData, loading: stepLoading, refetch: refetchStep } = useQuery(GET_STEP, {
variables: { id: selectedStep?.id },
- skip: !selectedStep,
+ skip: !selectedStep?.id,
});
const [printTask] = useMutation(PRINT_TASK);
const [printStep] = useMutation(PRINT_STEP);
const [createNote] = useMutation(CREATE_NOTE);
+ const [createGroup] = useMutation(CREATE_GROUP);
+ const [createTask] = useMutation(CREATE_TASK);
+ const [createStep] = useMutation(CREATE_STEP);
const handlePrintTask = async (taskId: string, userId: string) => {
+ if (!taskId || !userId) {
+ console.error('Cannot print task: missing taskId or userId');
+ return;
+ }
+
try {
await printTask({ variables: { id: taskId, userId } });
} catch (error) {
@@ -97,6 +142,11 @@ export function useTaskData() {
};
const handlePrintStep = async (stepId: string, userId: string) => {
+ if (!stepId || !userId) {
+ console.error('Cannot print step: missing stepId or userId');
+ return;
+ }
+
try {
await printStep({ variables: { id: stepId, userId } });
} catch (error) {
@@ -105,7 +155,10 @@ export function useTaskData() {
};
const handleAddNote = async (content: string, userId: string) => {
- if (!selectedStep) return;
+ if (!selectedStep?.id || !userId) {
+ console.error('Cannot add note: missing stepId or userId');
+ return;
+ }
try {
await createNote({
@@ -120,10 +173,67 @@ export function useTaskData() {
}
};
+ const handleCreateGroup = async (name: string) => {
+ if (!name) {
+ console.error('Cannot create group: missing name');
+ return;
+ }
+
+ try {
+ await createGroup({ variables: { name } });
+ await refetchGroups();
+ } catch (error) {
+ console.error('Error creating group:', error);
+ }
+ };
+
+ const handleCreateTask = async (name: string, groupId: string) => {
+ if (!name || !groupId) {
+ console.error('Cannot create task: missing name or groupId');
+ return;
+ }
+
+ try {
+ const variables = {
+ name,
+ groupId,
+ };
+ console.log('Creating task with variables:', variables);
+ await createTask({ variables });
+ await refetchTasks({ groupId });
+ } catch (error) {
+ console.error('Error creating task:', error);
+ }
+ };
+
+ const handleCreateStep = async (name: string, instructions: string, taskId: string, order: number) => {
+ if (!name || !instructions || !taskId || order < 1) {
+ console.error('Cannot create step: missing required fields');
+ return;
+ }
+
+ try {
+ await createStep({ variables: { name, instructions, taskId, order } });
+ await refetchStep({ id: taskId });
+ } catch (error) {
+ console.error('Error creating step:', error);
+ }
+ };
+
+ const groups = isArray(groupsData?.groups)
+ ? groupsData.groups.map((group: GraphQLGroup) => toGroupWithTasks(group)).filter(doesExist)
+ : [];
+
+ const tasks = isArray(tasksData?.tasks)
+ ? tasksData.tasks.map((task: GraphQLTask) => toTaskWithSteps(task)).filter(doesExist)
+ : [];
+
+ const step = stepData?.step ? toStepWithNotes(stepData.step) : undefined;
+
return {
- groups: (groupsData?.groups || []).map(toGroupWithTasks),
- tasks: (tasksData?.tasks || []).map(toTaskWithSteps),
- step: stepData?.step ? toStepWithNotes(stepData.step) : undefined,
+ groups,
+ tasks,
+ step,
loading: groupsLoading || tasksLoading || stepLoading,
selectedGroup,
selectedTask,
@@ -134,5 +244,8 @@ export function useTaskData() {
handlePrintTask,
handlePrintStep,
handleAddNote,
+ handleCreateGroup,
+ handleCreateTask,
+ handleCreateStep,
};
}
\ No newline at end of file
diff --git a/client/src/layouts/DesktopLayout.tsx b/client/src/layouts/DesktopLayout.tsx
index 85593f1..3578c82 100644
--- a/client/src/layouts/DesktopLayout.tsx
+++ b/client/src/layouts/DesktopLayout.tsx
@@ -1,5 +1,6 @@
import { Box, Typography } from '@mui/material';
import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types';
+import { CreateButtons } from '../components/CreateButtons';
interface DesktopLayoutProps {
groups: GroupWithTasks[];
@@ -24,9 +25,10 @@ export function DesktopLayout({
{/* Groups panel */}
-
- Groups
-
+
+ Groups
+
+
{groups.map((group) => (
-
- Tasks
-
+
+ Tasks
+
+
{selectedGroup.tasks.map((task) => (
-
- Steps
-
+
+ Steps
+
+
{selectedTask.steps.map((step) => (
{isGroupView(selectedGroup) && (
<>
-
- Groups
-
+
+ Groups
+
+
{groupList.map((group) => (
-
- Tasks
-
+
+ Tasks
+
+
{taskList.map((task) => (
-
- Steps
-
+
+ Steps
+
+
{stepList.map((step) => (
{
- tasks: TaskWithSteps[];
-}
-
-export interface TaskWithSteps extends Omit {
+export interface TaskWithSteps {
+ id: number;
+ name: string;
+ group_id: number;
+ print_count: number;
+ last_printed_at?: Date | string | null;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
steps: StepWithNotes[];
notes: NoteWithUser[];
- printCount: number;
- lastPrintedAt: string | null;
}
-export interface StepWithNotes extends Omit {
+export interface StepWithNotes {
+ id: number;
+ name: string;
+ instructions: string;
+ task_id: number;
+ order: number;
+ print_count: number;
+ last_printed_at?: Date | string | null;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
notes: NoteWithUser[];
- printCount: number;
- lastPrintedAt: string | null;
}
-export interface NoteWithUser extends Omit {
- user?: SharedUser;
+export type Task = {
+ id: number;
+ name: string;
+ group_id: number;
+ print_count: number;
+ last_printed_at?: Date | string | null;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
+};
+
+export type Step = {
+ id: number;
+ name: string;
+ instructions: string;
+ task_id: number;
+ order: number;
+ print_count: number;
+ last_printed_at?: Date | string | null;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
+};
+
+// Define User, Note, and PrintHistory locally if needed
+
+export interface User {
+ id: number;
+ name: string;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
}
-export interface PrintHistoryWithDetails extends Omit {
- user?: SharedUser;
+export interface Note {
+ id: number;
+ content: string;
+ step_id?: number;
+ task_id?: number;
+ user_id?: number;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
+}
+
+export interface NoteWithUser extends Note {
+ user?: User;
+}
+
+export interface PrintHistory {
+ id: number;
+ user_id: number;
+ task_id?: number;
+ step_id?: number;
+ printed_at: Date | string;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
+}
+
+export interface PrintHistoryWithDetails extends PrintHistory {
+ user?: User;
task?: TaskWithSteps;
step?: StepWithNotes;
}
-// Re-export shared types with client-specific modifications
-export type Group = Omit;
-export type Task = Omit & { lastPrintedAt: string | null };
-export type Step = Omit & { lastPrintedAt: string | null };
-export type User = Omit;
-export type Note = Omit;
-export type PrintHistory = Omit;
\ No newline at end of file
+export interface Group {
+ id: number;
+ name: string;
+ parent_id?: number;
+ created_at?: Date | string | null;
+ updated_at?: Date | string | null;
+}
+
+export interface GroupWithTasks extends Group {
+ tasks: TaskWithSteps[];
+}
+
+// All types are now defined locally above. Remove all re-exports and duplicate type definitions at the bottom of the file.
\ No newline at end of file
diff --git a/client/src/utils/typeGuards.ts b/client/src/utils/typeGuards.ts
new file mode 100644
index 0000000..3d59068
--- /dev/null
+++ b/client/src/utils/typeGuards.ts
@@ -0,0 +1,164 @@
+import type { GroupWithTasks, TaskWithSteps, StepWithNotes } from '../types';
+
+interface GraphQLStep {
+ id: number;
+ name: string;
+ instructions: string;
+ taskId: number;
+ order: number;
+ notes: GraphQLNote[];
+ print_count: number;
+ last_printed_at: string | null;
+}
+
+interface GraphQLNote {
+ id: number;
+ content: string;
+ created_at: string;
+ updated_at: string;
+ user: {
+ id: number;
+ name: string;
+ };
+}
+
+interface GraphQLTask {
+ id: number;
+ name: string;
+ groupId: number;
+ steps: GraphQLStep[];
+ notes: GraphQLNote[];
+ print_count: number;
+ last_printed_at: string | null;
+}
+
+interface GraphQLGroup {
+ id: number;
+ name: string;
+ tasks: GraphQLTask[];
+ created_at: string;
+ updated_at: string;
+}
+
+// Generic type guard for non-null values
+export function doesExist(value: T | null | undefined): value is T {
+ return value !== null && value !== undefined;
+}
+
+// Type guard for arrays
+export function isArray(value: unknown): value is T[] {
+ return Array.isArray(value);
+}
+
+// Type guard for objects with required properties
+export function hasProperties(value: unknown, properties: (keyof T)[]): value is T {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ properties.every(prop => prop in value)
+ );
+}
+
+// Type guard for GraphQL objects
+export function isGraphQLStep(value: unknown): value is GraphQLStep {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ 'id' in value &&
+ 'name' in value &&
+ 'instructions' in value &&
+ 'taskId' in value &&
+ 'order' in value &&
+ 'notes' in value &&
+ 'print_count' in value &&
+ 'last_printed_at' in value
+ );
+}
+
+export function isGraphQLTask(value: unknown): value is GraphQLTask {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ 'id' in value &&
+ 'name' in value &&
+ 'groupId' in value &&
+ 'steps' in value &&
+ 'notes' in value &&
+ 'print_count' in value &&
+ 'last_printed_at' in value
+ );
+}
+
+export function isGraphQLGroup(value: unknown): value is GraphQLGroup {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ 'id' in value &&
+ 'name' in value &&
+ 'tasks' in value &&
+ 'created_at' in value &&
+ 'updated_at' in value
+ );
+}
+
+// Transformation functions
+export function toStepWithNotes(step: GraphQLStep | null | undefined): StepWithNotes | null {
+ if (!doesExist(step)) return null;
+
+ return {
+ id: step.id,
+ name: step.name,
+ instructions: step.instructions,
+ task_id: step.taskId,
+ order: step.order,
+ print_count: step.print_count,
+ last_printed_at: step.last_printed_at ? new Date(step.last_printed_at) : undefined,
+ notes: isArray(step.notes) ? step.notes.map(note => ({
+ id: note.id,
+ content: note.content,
+ created_at: new Date(note.created_at),
+ updated_at: new Date(note.updated_at),
+ created_by: note.user.id,
+ user: {
+ id: note.user.id,
+ name: note.user.name,
+ },
+ })) : [],
+ };
+}
+
+export function toTaskWithSteps(task: GraphQLTask | null | undefined): TaskWithSteps | null {
+ if (!doesExist(task)) return null;
+
+ return {
+ id: task.id,
+ name: task.name,
+ group_id: task.groupId,
+ print_count: task.print_count,
+ last_printed_at: task.last_printed_at ? new Date(task.last_printed_at) : undefined,
+ steps: isArray(task.steps) ? task.steps.map(step => toStepWithNotes(step)).filter(doesExist) : [],
+ notes: isArray(task.notes) ? task.notes.map(note => ({
+ id: note.id,
+ content: note.content,
+ created_at: new Date(note.created_at),
+ updated_at: new Date(note.updated_at),
+ created_by: note.user.id,
+ user: {
+ id: note.user.id,
+ name: note.user.name,
+ },
+ })) : [],
+ };
+}
+
+export function toGroupWithTasks(group: GraphQLGroup | null | undefined): GroupWithTasks | null {
+ if (!doesExist(group)) return null;
+
+ return {
+ id: group.id,
+ name: group.name,
+ created_at: new Date(group.created_at),
+ updated_at: new Date(group.updated_at),
+ tasks: isArray(group.tasks) ? group.tasks.map(task => toTaskWithSteps(task)).filter(doesExist) : [],
+ };
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index e248ab9..b5ceb27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -613,6 +613,28 @@
"node": ">=14"
}
},
+ "node_modules/@apollographql/apollo-tools": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz",
+ "integrity": "sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "graphql": "^14.2.1 || ^15.0.0 || ^16.0.0"
+ }
+ },
+ "node_modules/@apollographql/graphql-playground-html": {
+ "version": "1.6.29",
+ "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz",
+ "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==",
+ "license": "MIT",
+ "dependencies": {
+ "xss": "^1.0.8"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1971,6 +1993,21 @@
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
+ "node_modules/@graphql-tools/mock": {
+ "version": "8.7.20",
+ "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.20.tgz",
+ "integrity": "sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@graphql-tools/schema": "^9.0.18",
+ "@graphql-tools/utils": "^9.2.1",
+ "fast-json-stable-stringify": "^2.1.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
"node_modules/@graphql-tools/schema": {
"version": "9.0.19",
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz",
@@ -2390,6 +2427,12 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@josephg/resolvable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz",
+ "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==",
+ "license": "ISC"
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -3155,6 +3198,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3754,6 +3806,446 @@
"node": ">= 8"
}
},
+ "node_modules/apollo-datasource": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.2.tgz",
+ "integrity": "sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg==",
+ "deprecated": "The `apollo-datasource` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.keyvaluecache": "^1.0.1",
+ "apollo-server-env": "^4.2.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/apollo-datasource/node_modules/@apollo/utils.keyvaluecache": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz",
+ "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.logger": "^1.0.0",
+ "lru-cache": "7.10.1 - 7.13.1"
+ }
+ },
+ "node_modules/apollo-datasource/node_modules/@apollo/utils.logger": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz",
+ "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==",
+ "license": "MIT"
+ },
+ "node_modules/apollo-datasource/node_modules/lru-cache": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz",
+ "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/apollo-reporting-protobuf": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz",
+ "integrity": "sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog==",
+ "deprecated": "The `apollo-reporting-protobuf` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/usage-reporting-protobuf` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/protobufjs": "1.2.6"
+ }
+ },
+ "node_modules/apollo-reporting-protobuf/node_modules/@apollo/protobufjs": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.6.tgz",
+ "integrity": "sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/long": "^4.0.0",
+ "@types/node": "^10.1.0",
+ "long": "^4.0.0"
+ },
+ "bin": {
+ "apollo-pbjs": "bin/pbjs",
+ "apollo-pbts": "bin/pbts"
+ }
+ },
+ "node_modules/apollo-reporting-protobuf/node_modules/@types/node": {
+ "version": "10.17.60",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "license": "MIT"
+ },
+ "node_modules/apollo-server-core": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.13.0.tgz",
+ "integrity": "sha512-v/g6DR6KuHn9DYSdtQijz8dLOkP78I5JSVJzPkARhDbhpH74QNwrQ2PP2URAPPEDJ2EeZNQDX8PvbYkAKqg+kg==",
+ "deprecated": "The `apollo-server-core` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.keyvaluecache": "^1.0.1",
+ "@apollo/utils.logger": "^1.0.0",
+ "@apollo/utils.usagereporting": "^1.0.0",
+ "@apollographql/apollo-tools": "^0.5.3",
+ "@apollographql/graphql-playground-html": "1.6.29",
+ "@graphql-tools/mock": "^8.1.2",
+ "@graphql-tools/schema": "^8.0.0",
+ "@josephg/resolvable": "^1.0.0",
+ "apollo-datasource": "^3.3.2",
+ "apollo-reporting-protobuf": "^3.4.0",
+ "apollo-server-env": "^4.2.1",
+ "apollo-server-errors": "^3.3.1",
+ "apollo-server-plugin-base": "^3.7.2",
+ "apollo-server-types": "^3.8.0",
+ "async-retry": "^1.2.1",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graphql-tag": "^2.11.0",
+ "loglevel": "^1.6.8",
+ "lru-cache": "^6.0.0",
+ "node-abort-controller": "^3.0.1",
+ "sha.js": "^2.4.11",
+ "uuid": "^9.0.0",
+ "whatwg-mimetype": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "graphql": "^15.3.0 || ^16.0.0"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.dropunuseddefinitions": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz",
+ "integrity": "sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.keyvaluecache": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz",
+ "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.logger": "^1.0.0",
+ "lru-cache": "7.10.1 - 7.13.1"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.keyvaluecache/node_modules/lru-cache": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz",
+ "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.logger": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz",
+ "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==",
+ "license": "MIT"
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.printwithreducedwhitespace": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz",
+ "integrity": "sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.removealiases": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz",
+ "integrity": "sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.sortast": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz",
+ "integrity": "sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.sortby": "^4.7.0"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.stripsensitiveliterals": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz",
+ "integrity": "sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@apollo/utils.usagereporting": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz",
+ "integrity": "sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/usage-reporting-protobuf": "^4.0.0",
+ "@apollo/utils.dropunuseddefinitions": "^1.1.0",
+ "@apollo/utils.printwithreducedwhitespace": "^1.1.0",
+ "@apollo/utils.removealiases": "1.0.0",
+ "@apollo/utils.sortast": "^1.1.0",
+ "@apollo/utils.stripsensitiveliterals": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "graphql": "14.x || 15.x || 16.x"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@graphql-tools/merge": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.3.1.tgz",
+ "integrity": "sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@graphql-tools/utils": "8.9.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@graphql-tools/schema": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.5.1.tgz",
+ "integrity": "sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@graphql-tools/merge": "8.3.1",
+ "@graphql-tools/utils": "8.9.0",
+ "tslib": "^2.4.0",
+ "value-or-promise": "1.0.11"
+ },
+ "peerDependencies": {
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/@graphql-tools/utils": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.9.0.tgz",
+ "integrity": "sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/apollo-server-core/node_modules/value-or-promise": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz",
+ "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/apollo-server-env": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz",
+ "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==",
+ "deprecated": "The `apollo-server-env` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/utils.fetcher` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "node-fetch": "^2.6.7"
+ },
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/apollo-server-errors": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz",
+ "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==",
+ "deprecated": "The `apollo-server-errors` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "graphql": "^15.3.0 || ^16.0.0"
+ }
+ },
+ "node_modules/apollo-server-express": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.13.0.tgz",
+ "integrity": "sha512-iSxICNbDUyebOuM8EKb3xOrpIwOQgKxGbR2diSr4HP3IW8T3njKFOoMce50vr+moOCe1ev8BnLcw9SNbuUtf7g==",
+ "deprecated": "The `apollo-server-express` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@types/accepts": "^1.3.5",
+ "@types/body-parser": "1.19.2",
+ "@types/cors": "2.8.12",
+ "@types/express": "4.17.14",
+ "@types/express-serve-static-core": "4.17.31",
+ "accepts": "^1.3.5",
+ "apollo-server-core": "^3.13.0",
+ "apollo-server-types": "^3.8.0",
+ "body-parser": "^1.19.0",
+ "cors": "^2.8.5",
+ "parseurl": "^1.3.3"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "express": "^4.17.1",
+ "graphql": "^15.3.0 || ^16.0.0"
+ }
+ },
+ "node_modules/apollo-server-express/node_modules/@types/body-parser": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+ "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/apollo-server-express/node_modules/@types/cors": {
+ "version": "2.8.12",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
+ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
+ "license": "MIT"
+ },
+ "node_modules/apollo-server-express/node_modules/@types/express": {
+ "version": "4.17.14",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz",
+ "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.18",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/apollo-server-express/node_modules/@types/express-serve-static-core": {
+ "version": "4.17.31",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz",
+ "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*"
+ }
+ },
+ "node_modules/apollo-server-plugin-base": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz",
+ "integrity": "sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw==",
+ "deprecated": "The `apollo-server-plugin-base` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "apollo-server-types": "^3.8.0"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "graphql": "^15.3.0 || ^16.0.0"
+ }
+ },
+ "node_modules/apollo-server-types": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.8.0.tgz",
+ "integrity": "sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A==",
+ "deprecated": "The `apollo-server-types` package is part of Apollo Server v2 and v3, which are now end-of-life (as of October 22nd 2023 and October 22nd 2024, respectively). This package's functionality is now found in the `@apollo/server` package. See https://www.apollographql.com/docs/apollo-server/previous-versions/ for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.keyvaluecache": "^1.0.1",
+ "@apollo/utils.logger": "^1.0.0",
+ "apollo-reporting-protobuf": "^3.4.0",
+ "apollo-server-env": "^4.2.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "graphql": "^15.3.0 || ^16.0.0"
+ }
+ },
+ "node_modules/apollo-server-types/node_modules/@apollo/utils.keyvaluecache": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz",
+ "integrity": "sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@apollo/utils.logger": "^1.0.0",
+ "lru-cache": "7.10.1 - 7.13.1"
+ }
+ },
+ "node_modules/apollo-server-types/node_modules/@apollo/utils.logger": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-1.0.1.tgz",
+ "integrity": "sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA==",
+ "license": "MIT"
+ },
+ "node_modules/apollo-server-types/node_modules/lru-cache": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz",
+ "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -4648,6 +5140,12 @@
"node": ">= 8"
}
},
+ "node_modules/cssfilter": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
+ "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
+ "license": "MIT"
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -5594,7 +6092,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
@@ -10591,6 +11088,28 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/xss": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
+ "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^2.20.3",
+ "cssfilter": "0.0.10"
+ },
+ "bin": {
+ "xss": "bin/xss"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/xss/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -10698,6 +11217,7 @@
"dependencies": {
"@apollo/server": "^4.10.0",
"@task-receipts/shared": "file:../shared",
+ "apollo-server-express": "^3.13.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
diff --git a/server/package.json b/server/package.json
index 1c8ea1e..21e2e70 100644
--- a/server/package.json
+++ b/server/package.json
@@ -12,11 +12,15 @@
"test:coverage": "jest --coverage",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit",
+ "migrate:latest": "knex migrate:latest --knexfile src/db/knexfile.ts",
+ "migrate:rollback": "knex migrate:rollback --knexfile src/db/knexfile.ts",
+ "migrate:make": "knex migrate:make --knexfile src/db/knexfile.ts"
},
"dependencies": {
"@apollo/server": "^4.10.0",
"@task-receipts/shared": "file:../shared",
+ "apollo-server-express": "^3.13.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
diff --git a/server/src/db/knexfile.ts b/server/src/db/knexfile.ts
index c5e96a9..432cda6 100644
--- a/server/src/db/knexfile.ts
+++ b/server/src/db/knexfile.ts
@@ -1,25 +1,26 @@
import type { Knex } from 'knex';
+import path from 'path';
const config: { [key: string]: Knex.Config } = {
development: {
client: 'sqlite3',
connection: {
- filename: './dev.sqlite3'
+ filename: path.join(__dirname, '../../../dev.sqlite3'),
},
useNullAsDefault: true,
migrations: {
- directory: './src/db/migrations'
- }
+ directory: path.join(__dirname, 'migrations'),
+ },
},
test: {
client: 'sqlite3',
connection: {
- filename: ':memory:'
+ filename: ':memory:',
},
useNullAsDefault: true,
migrations: {
- directory: './src/db/migrations'
- }
+ directory: path.join(__dirname, 'migrations'),
+ },
},
production: {
client: 'sqlite3',
diff --git a/server/src/graphql/schema.ts b/server/src/graphql/schema.ts
index 2f30c2a..16ba1be 100644
--- a/server/src/graphql/schema.ts
+++ b/server/src/graphql/schema.ts
@@ -1,70 +1,72 @@
-import { gql } from 'graphql-tag';
+import { gql } from 'apollo-server-express';
export const typeDefs = gql`
type Group {
id: ID!
name: String!
- parentId: ID
+ parent_id: ID
tasks: [Task!]!
- createdAt: String!
- updatedAt: String!
+ created_at: String!
+ updated_at: String!
}
type Task {
id: ID!
name: String!
- groupId: ID!
+ group_id: ID!
group: Group!
steps: [Step!]!
- printCount: Int!
- lastPrintedAt: String
- createdAt: String!
- updatedAt: String!
notes: [Note!]!
+ print_count: Int!
+ last_printed_at: String
printHistory: [PrintHistory!]!
+ created_at: String!
+ updated_at: String!
}
type Step {
id: ID!
name: String!
instructions: String!
- taskId: ID!
+ task_id: ID!
task: Task!
order: Int!
- images: [Image!]!
- printCount: Int!
- lastPrintedAt: String
- createdAt: String!
- updatedAt: String!
notes: [Note!]!
+ print_count: Int!
+ last_printed_at: String
printHistory: [PrintHistory!]!
+ images: [Image!]!
+ created_at: String!
+ updated_at: String!
}
type Image {
id: ID!
- stepId: ID!
- originalPath: String!
- bwPath: String!
+ step_id: ID!
+ step: Step!
+ original_path: String!
+ bw_path: String!
order: Int!
- createdAt: String!
- updatedAt: String!
+ created_at: String!
+ updated_at: String!
}
type User {
id: ID!
name: String!
- createdAt: String!
- updatedAt: String!
+ created_at: String!
+ updated_at: String!
}
type Note {
id: ID!
content: String!
- taskId: ID
- stepId: ID
+ step_id: ID!
+ step: Step!
+ user: User!
createdBy: User!
- createdAt: String!
- updatedAt: String!
+ created_at: String!
+ updated_at: String!
}
type PrintHistory {
@@ -72,9 +74,9 @@ export const typeDefs = gql`
user: User!
task: Task
step: Step
- printedAt: String!
- createdAt: String!
- updatedAt: String!
+ printed_at: String!
+ created_at: String!
+ updated_at: String!
}
type Query {
@@ -88,7 +90,7 @@ export const typeDefs = gql`
frequentTasks: [Task!]!
users: [User!]!
user(id: ID!): User
- notes(taskId: ID, stepId: ID): [Note!]!
+ notes(stepId: ID!): [Note!]!
printHistory(taskId: ID, stepId: ID): [PrintHistory!]!
}
@@ -98,8 +100,8 @@ export const typeDefs = gql`
createStep(name: String!, instructions: String!, taskId: ID!, order: Int!): Step!
createImage(stepId: ID!, originalPath: String!, bwPath: String!, order: Int!): Image!
createUser(name: String!): User!
- createNote(content: String!, taskId: ID, stepId: ID, createdBy: ID!): Note!
- printTask(id: ID!, userId: ID!): Task!
- printStep(id: ID!, userId: ID!): Step!
+ createNote(content: String!, stepId: ID!): Note!
+ printTask(id: ID!): Task!
+ printStep(id: ID!): Step!
}
`;
\ No newline at end of file