You are an expert software engineer specialised in Next.js 14 with TypeScript. Complete my `createSubmission` function. I’ll provide you with my code implementations for reference. But focus on the TODOs.

app/submissions/[submissionUrl]/submit/page.tsx

```tsx
import { retrieveQuestionTree } from "@/app/actions/server/default.action";
import Submission from "@/database/models/submission.model";
import Submit from ".";

interface IProps {
  params: { submissionUrl: string };
}

export default async function Page({ params: { submissionUrl } }: Readonly<IProps>) {
  const submission = await Submission.findOne({ $or: [{ _id: submissionUrl }, { invite: submissionUrl }] }, { questionId: 1 })
    .lean()
    .exec();
  if (!submission) throw new Error("");

  const questionTree = (await retrieveQuestionTree([submission.questionId]))[0];

  return <Submit submissionId={submission._id} questionTree={questionTree} />;
}

const createSubmission = async (): Promise<void> => {
  /**
   * TODO: Create a new assessment document.
   * Details:
   *  - Properties to set: questionId, submissionId and publishedAt.
   *  - questionId will be the same as the one defined in the submission document.
   *  - submissionId will be the _id of the submission document.
   *  - publishedAt will be Date.now()
   */
  /**
   * TODO: Create initialise attempt documents.
   * Details:
   *  - Use the `initAssessmentAttempts` function from the `app/actions/server/default.action.ts` file.
   *  - Extend the `initAssessmentAttempts` function so it can accept optional properties. We want to set the submittedAt property while initialising attempts.
   *  - What the `initAssessmentAttempts` does essentially creates an attempt for each question in the question tree where the parent question is the questionId specified in the assessment document.
   */
  /**
   * TODO: Upload the images.
   * Details:
   *  - Create a server function for creating file documents, essentially uploading files.
   *  - Use the ID generated for the client as the ID for the documents.
   */
  /**
   * TODO: Assign the file IDs to the right attempts.
   * Details:
   *  - The imagesArray is an array of images. Where each set of images correspond to a question.
   *  - There is container variable which specifies which questionId the set of images belongs too.
   *  - When the length of containers is more than one, the first set of images is a staging element which does not correspond to any questionId. Images in staging should not be stored (no file documents are created for images left in the staging images). They are also not attached to any attempts.
   *  - When the length of containers is one, all images correspond to the question specified in the single element of the containers array.
   *  - The attempt that will be created will have the ID of the assessment document and their respective question ID. You can find the right attempt to assign the images to using the ID of the newly created assessment document, and the question ID of the container (corresponds with a set of images).
   *  - You assign the images by populating the answers array of the attempt. Where the type of content is image, and the value is `https//app.ehllie.com/api/file/image?fileId=${fileId}`. Ensure the order of images is maintained.
   *  - Save the attempts once the images have been assigned to them.
   */
  /**
   * TODO: Revalidate the /submissions/[submissionUrl] page
   */
};
```

database/models/submission.model.ts

```tsx
import mongoose, { Document, Model, Schema } from "mongoose";
import { v4 } from "uuid";

export interface ISubmission {
  _id: string;
  teacherId: string;
  questionId: string;
  invite: string;
  title: string;
  createdAt: number;
}

const SubmissionSchema: Schema = new Schema(
  {
    _id: { type: String, default: v4 },
    teacherId: { type: String, ref: "User", required: true },
    questionId: { type: String, ref: "Question", required: true },
    invite: { type: String, unique: true, required: true },
    title: { type: String, default: "Untitled submissions" },
    createdAt: { type: Number, default: Date.now },
  },
  { _id: false }
);

const Submission: Model<ISubmission & Document> =
  mongoose.models.Submission || mongoose.model<ISubmission & Document>("Submission", SubmissionSchema);

export default Submission;
```

database/models/assessment.model.ts

```tsx
import mongoose, { Schema, Document, Model } from "mongoose";
import { v4 } from "uuid";

// Define the Assessment interface
export interface IAssessment {
  _id: string;
  questionId: string;
  userId?: string;
  submissionId?: string;
  isConfigured: boolean;
  createdAt: number;
  publishedAt?: number;
}

// Define the Assessment schema
const AssessmentSchema: Schema = new Schema(
  {
    _id: { type: String, default: v4 },
    questionId: { type: String, ref: "Question", required: true },
    userId: { type: String, ref: "User" },
    submissionId: { type: String, ref: "Submission" },
    isConfigured: { type: Boolean, default: false },
    createdAt: { type: Number, default: Date.now },
    publishedAt: { type: Number },
  },
  { _id: false } // Disable Mongoose auto _id generation, using UUID
);

// Create the Assessment model
const Assessment: Model<IAssessment & Document> =
  mongoose.models.Assessment || mongoose.model<IAssessment & Document>("Assessment", AssessmentSchema);

export default Assessment;
```

database/models/question.model.ts

```tsx
import mongoose, { Document, Model, Schema } from "mongoose";
import { ContentSchema, IContent } from "../schemas/content.schema";
import { v4 } from "uuid";

export const QuestionTypeKeys = ["open-ended", "section"] as const;

export type TQuestionType = (typeof QuestionTypeKeys)[number];

export interface ICriterion {
  grade: string;
  criterion: IContent[];
}

const CriterionSchema: Schema = new Schema({ grade: { type: String }, criterion: { type: [ContentSchema], default: [] } }, { _id: false });

export interface ICriteria {
  information: IContent[];
  schedule: ICriterion[];
}

const CriteriaSchema: Schema = new Schema(
  { information: { type: [ContentSchema], default: [] }, schedule: { type: [CriterionSchema], default: [] } },
  { _id: false }
);

export interface IQuestion {
  _id: string;
  authorId: string;
  statement: IContent[];
  type: TQuestionType;
  criteria?: ICriteria;
  questionIds: string[];
  createdAt: string;
  publishedAt?: string;
}

const QuestionSchema: Schema = new Schema(
  {
    _id: { type: String, default: v4 },
    authorId: { type: String, ref: "User", required: true },
    statement: { type: [ContentSchema], default: [] },
    type: { type: String, enum: QuestionTypeKeys, required: true },
    criteria: { type: CriteriaSchema },
    questionIds: { type: [{ type: String, required: true }], default: [] },
    createdAt: { type: Number, default: Date.now },
    publishedAt: { type: Number },
  },
  { _id: false }
);

const Question: Model<IQuestion & Document> = mongoose.models.Question || mongoose.model<IQuestion & Document>("Question", QuestionSchema);

export default Question;
```

database/models/attempt.model.ts

```tsx
import mongoose, { Document, Model, Schema } from "mongoose";
import { ContentSchema, IContent } from "../schemas/content.schema";
import { v4 } from "uuid";

export interface IAttempt {
  _id: string;
  assessmentId: string;
  questionId: string;
  answer: IContent[];
  grade: string;
  feedback: IContent[];
  createdAt: number;
  updatedAt: number;
  submittedAt?: number;
  markedAt?: number;
  verifiedAt?: number;
}

const AttemptSchema: Schema = new Schema(
  {
    _id: { type: String, default: v4 },
    assessmentId: { type: String, ref: "Assessment", required: true },
    questionId: { type: String, ref: "Question", required: true },
    answer: { type: [ContentSchema], default: [] },
    grade: { type: String, default: "" },
    feedback: { type: [ContentSchema], default: [] },
    createdAt: { type: Number, default: Date.now },
    updatedAt: { type: Number, default: Date.now },
    submittedAt: { type: Number },
    markedAt: { type: Number },
    verifiedAt: { type: Number },
  },
  { _id: false }
);

const Attempt: Model<IAttempt & Document> = mongoose.models.Attempt || mongoose.model<IAttempt & Document>("Attempt", AttemptSchema);

export default Attempt;
```

app/actions/server/default.action.ts

```tsx
import "server-only";

import Question, { ICriteria, TQuestionType } from "@/database/models/question.model";
import { IContent } from "@/database/schemas/content.schema";
import { ClientSession } from "mongoose";
import Attempt from "@/database/models/attempt.model";

export interface IQuestionBranch {
  id: string;
  type: TQuestionType;
  statement: IContent[];
  questions: IQuestionBranch[];
  criteria?: ICriteria;
}

export const retrieveQuestionTree = async (questionIds: string[]): Promise<IQuestionBranch[]> => {
  const questions = await Question.find({ _id: { $in: questionIds } }, { _id: 1, type: 1, statement: 1, questionIds: 1, criteria: 1 })
    .lean()
    .exec();
  const questionBranches: IQuestionBranch[] = [];
  for (let i = 0; i < questions.length; i++) {
    const question = questions[i];
    const subQuestionBranches: IQuestionBranch[] = await retrieveQuestionTree(question.questionIds);
    questionBranches.push({
      id: question._id,
      type: question.type,
      statement: question.statement,
      questions: subQuestionBranches,
      criteria: question.criteria,
    });
  }
  return questionBranches;
};

export const initAssessmentAttempts = async (assessmentId: string, questionId: string, session: ClientSession): Promise<void> => {
  const question = await Question.findById(questionId, { questionIds: 1 }).lean().exec();
  if (!question) throw new Error("");
  const attempt = await Attempt.exists({ assessmentId, questionId });
  if (!!attempt) throw new Error("");
  const newAttempt = new Attempt({ assessmentId, questionId });
  await newAttempt.save({ session });
  for (let i = 0; i < question.questionIds.length; i++) {
    await initAssessmentAttempts(assessmentId, question.questionIds[i], session);
  }
};
```

app/submissions/[submissionUrl]/submit/images-containers.tsx

```tsx
"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { UploadIcon } from "lucide-react";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { PDFPageProxy } from "pdfjs-dist";
import { useGlobalContext } from "@/app/context";
import { IQuestionBranch } from "@/app/actions/server/default.action";
import ImagesContainers from "./images-containers";
import { createHierarchicalLabel } from "@/lib/utils";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
import Link from "next/link";
import { v4 as uuidv4 } from "uuid"; // Import UUID

/**
 * Define the IImage interface with a unique ID and File
 */
export interface IImage {
  id: string;
  file: File;
}

/**
 * Convert a single PDF page to an image and adjust its quality.
 *
 * @param pdfPage - The page object from PDF.js
 * @param pdfFileName - The original PDF file name for naming the image files
 * @param pageNum - The page number to keep track of in the file name
 * @param scale - Scale for rendering the page (default is 1)
 * @returns The compressed image file for the PDF page
 */
export const convertPdfPageToImage = async (
  pdfPage: PDFPageProxy, // pdf.js page object
  pdfFileName: string,
  pageNum: number,
  scale: number = 1
): Promise<File> => {
  try {
    // Create a canvas to render the page
    const viewport = pdfPage.getViewport({ scale });
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("2d");

    canvas.width = viewport.width;
    canvas.height = viewport.height;

    // Render the PDF page into the canvas
    await pdfPage.render({
      canvasContext: context!,
      viewport,
    }).promise;

    // Convert the canvas content to a Blob (image format)
    const blob = await new Promise<Blob>((resolve) => {
      canvas.toBlob((blob) => resolve(blob!), "image/jpeg");
    });

    // Create a File object from the Blob
    const imageFile = new File([blob], `${pdfFileName}_page_${pageNum}.jpg`, {
      type: "image/jpeg",
    });

    return imageFile;
  } catch (error) {
    console.error("Error converting PDF page to image:", error);
    throw new Error("Failed to convert PDF page to image");
  }
};

export interface IContainer {
  label: string;
  questionId?: string;
  preview?: string;
}

interface IProps {
  submissionId?: string;
  questionTree: IQuestionBranch;
}

const Submit = ({ submissionId, questionTree }: IProps) => {
  const containers = useMemo<IContainer[]>(() => {
    const newContainers: IContainer[] = [];
    const findContainer = (
      newContainers: IContainer[],
      questionBranch: IQuestionBranch,
      labeller?: { ancestors: number[]; index: number }
    ): void => {
      if (!questionBranch.criteria) {
        for (let i = 0; i < questionBranch.questions.length; i++) {
          findContainer(
            newContainers,
            questionBranch.questions[i],
            !labeller ? { ancestors: [], index: i } : { ancestors: [...labeller.ancestors, labeller.index], index: i }
          );
        }
      } else {
        newContainers.push({
          label: !labeller ? "" : `Question ${createHierarchicalLabel(labeller.ancestors, labeller.index)}`,
          questionId: questionBranch.id,
          preview: "",
        });
      }
    };
    findContainer(newContainers, questionTree);
    return newContainers;
    // eslint-disable-next-line
  }, []);

  const openRef = useRef(false);
  const inputRef = useRef<HTMLInputElement>(null);

  const { pdfjsLib } = useGlobalContext();

  const [open, setOpen] = useState(false);
  const [converting, setConverting] = useState(false);
  const [imagesArray, setImagesArray] = useState<IImage[][]>(containers.length > 1 ? [[], ...containers.map(() => [])] : [[]]);

  openRef.current = open;

  const handleUploadFiles = useCallback(
    async (event: React.ChangeEvent<HTMLInputElement>) => {
      const files = event.target.files;

      if (!files) {
        // No files selected, do nothing
        return;
      }

      try {
        setOpen(true);
        setConverting(true); // Set state to indicate conversion in progress

        const newImages: IImage[] = []; // Updated to use IImage

        // Loop through each file uploaded by the user
        for (let i = 0; i < files.length; i++) {
          const file = files[i];

          if (file.type === "application/pdf") {
            // Load the PDF using pdf.js
            const pdf = await pdfjsLib!.getDocument(URL.createObjectURL(file)).promise;
            const numPages = pdf.numPages;

            // Convert each page of the PDF to an image, in sequential order
            for (let pageNum = 1; pageNum <= numPages; pageNum++) {
              const page = await pdf.getPage(pageNum);

              // Convert each page to an image and adjust quality
              const image = await convertPdfPageToImage(page, file.name, pageNum);

              if (!openRef.current) return setConverting(false); // Cancel if sheet is closed

              // Assign a unique ID to each image
              newImages.push({ id: uuidv4(), file: image });
            }
          } else {
            // For image files, directly assign a unique ID
            newImages.push({ id: uuidv4(), file });
          }
        }

        // Update the state in the order of PDF pages or added images
        setImagesArray((prevImagesArray) => {
          const newImagesArray = [...prevImagesArray];
          newImagesArray[0] = [...newImagesArray[0], ...newImages];
          return newImagesArray;
        });
      } catch (error) {
        console.error("Error during file upload and conversion:", error);
      } finally {
        setConverting(false); // Reset state when conversion is done
        if (inputRef.current) {
          inputRef.current.value = ""; // Clear the file input
        }
      }
    },
    [pdfjsLib]
  );

  return (
    <>
      <div className="h-32 w-full flex shrink-0 overflow-hidden items-center pt-8 px-8 lg:px-24 gap-4 lg:gap-8">
        {!!submissionId && (
          <Link href={`/submissions/${submissionId}`}>
            <Button variant="ghost">
              <ChevronLeftIcon className="h-5 w-5 mr-2" />
              Cancel
            </Button>
          </Link>
        )}
        <h1 className="w-full text-2xl font-medium">Create a Submission</h1>
        <Label>
          <Button asChild disabled={converting}>
            <span className="flex items-center">
              <UploadIcon className="h-5 w-5 mr-2" />
              Upload PDFs or Images
            </span>
          </Button>
          <Input
            type="file"
            accept="image/*,application/pdf"
            onChange={handleUploadFiles}
            className="hidden"
            disabled={converting}
            ref={inputRef}
            multiple
          />
        </Label>
      </div>
      <ImagesContainers containers={containers} imagesArray={imagesArray} setImagesArray={setImagesArray} />
    </>
  );
};

export default Submit;
```