{
  "openapi": "3.0.3",
  "info": {
    "title": "AFCT Dashboard API",
    "version": "0.2.1",
    "description": "Auto-generated reference for the AFCT Dashboard API. Paths and methods are inferred\nfrom the route files so the docs stay in step with the code; individual endpoints are\nenriched with an `@openapi` block in the source.\n\n## Authentication\n\nThe API authenticates with the **NextAuth session cookie**. Most endpoints require a\nsigned-in session; some additionally require an `ADMIN`, `FACULTY`, or `TA` role, and the\nrequirement is stated on each operation. A few endpoints (health, signup, login, public\nsettings) need no session.\n\n## Conventions\n\n- **Base URL:** same-origin, relative to the deployed app.\n- **Errors:** failures return a JSON body with an `error` or `message` string (see the\n  `Error` schema).\n- **Dates:** `datetime-local` strings (`YYYY-MM-DDTHH:MM`) are interpreted in the\n  actor's timezone and stored as UTC.\n- Every operation links to its **source** on GitHub."
  },
  "servers": [
    {
      "url": "/",
      "description": "Same-origin (relative to the deployed app)"
    }
  ],
  "tags": [
    {
      "name": "admin",
      "description": "Admin-only user, submission, log, and system tools."
    },
    {
      "name": "assignments",
      "description": "An assignment's problem list (global lookup by assignment id)."
    },
    {
      "name": "auth",
      "description": "Sign-up and credential/email checks."
    },
    {
      "name": "client"
    },
    {
      "name": "comments",
      "description": "An assignment problem's discussion comments."
    },
    {
      "name": "courses",
      "description": "Courses, rosters, enrollment, groups, grades, and their assignments and problems."
    },
    {
      "name": "files",
      "description": "Served avatars, uploaded files, and solution files."
    },
    {
      "name": "health",
      "description": "Liveness probe for the container healthcheck."
    },
    {
      "name": "me",
      "description": "The signed-in user's own profile, password, courses, enrollments, and assignments."
    },
    {
      "name": "session",
      "description": "Session keep-alive."
    },
    {
      "name": "submissions",
      "description": "Student submissions and re-evaluation."
    },
    {
      "name": "system-settings",
      "description": "System configuration, TLS certificates, and backups."
    },
    {
      "name": "users",
      "description": "User accounts and administration."
    }
  ],
  "paths": {
    "/api/admin/logs/export/fields": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminLogsExportFields",
        "summary": "List exportable log fields",
        "description": "Lists the activity-log columns that may be included in a CSV export; drives the  Download dialog's field picker. Nested under `export` because it describes what  the sibling `POST /admin/logs/export` accepts. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/logs/export/fields/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The exportable field names.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/logs/export": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminLogsExport",
        "summary": "Export activity logs",
        "description": "Returns the selected activity-log columns within a time range, for CSV export.  System administrators only. Column names are validated against the exportable allow-list  before reaching the Prisma select (guards field injection), and the result is  capped at MAX_EXPORT_ROWS so one export can't page the whole table into memory.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/logs/export/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cols",
                  "begTime",
                  "endTime"
                ],
                "properties": {
                  "cols": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Field names from getFields"
                  },
                  "begTime": {
                    "type": "string",
                    "description": "Start of range (datetime-local; ignored if unparseable)"
                  },
                  "endTime": {
                    "type": "string",
                    "description": "End of range (datetime-local; ignored if unparseable)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching log rows with only the requested columns.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, failed validation, or no valid fields selected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Export failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/logs": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminLogs",
        "summary": "List activity (audit) logs",
        "description": "A single page of activity (audit) logs, newest first, with `userId` resolved to  the author's display name. Search, severity filter, and sort all run server-side.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/logs/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Match on action, category, or author name/email",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "field",
            "in": "query",
            "description": "Restrict the search to one field",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "action",
                "category",
                "name",
                "email"
              ]
            }
          },
          {
            "name": "severity",
            "in": "query",
            "description": "Repeatable; filters to any of the given levels",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INFO",
                  "WARNING",
                  "ERROR",
                  "SECURITY"
                ]
              }
            }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Repeatable; filters to any of the given categories",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "SYSTEM",
                  "USER",
                  "COURSE",
                  "ASSIGNMENT",
                  "PROBLEM",
                  "SUBMISSION",
                  "GRADE"
                ]
              }
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "timestamp",
                "severity",
                "category",
                "action",
                "ipAddress",
                "userLastName",
                "userFirstName"
              ]
            }
          },
          {
            "name": "sortDir",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of logs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "ActivityLog rows; userId is the resolved author name"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    },
                    "totalPages": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Query failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/reset-password": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminResetPassword",
        "summary": "Reset a user's password (admin)",
        "description": "Sets another user's password on their behalf (an admin-initiated reset).  System administrators only; the new password still has to meet the strength  policy. Pass `isTemporary` to force a change at next login. The plaintext  password is never logged, only who reset whom.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/reset-password/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId",
                  "newPassword"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "The user whose password is being reset"
                  },
                  "newPassword": {
                    "type": "string",
                    "description": "Must meet the strength policy"
                  },
                  "isTemporary": {
                    "type": "boolean",
                    "description": "Force a change at next login (default false)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password reset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields or weak password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Target user not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Reset failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/backups/download": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsBackupsDownload",
        "summary": "Download a backup file",
        "description": "Streams a single backup file to the caller as an attachment. System administrators only.  A database dump contains the entire database (password hashes and all PII), so  the download is always recorded as a SECURITY audit event. The filename is  checked against a strict allow-list and the resolved path must stay inside the  backup directory: two independent guards against path traversal.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/backups/download/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "file",
            "in": "query",
            "required": true,
            "description": "Exact backup filename from the list endpoint.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The backup file as an octet-stream attachment.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Filename failed the allow-list or path check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The backup file does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/backups": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsBackups",
        "summary": "List backups",
        "description": "Lists available backups, newest first, each pairing a database dump with its  matching upload-files archive. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/backups/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The available backup pairs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "backups": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminSettingsBackups",
        "summary": "Trigger a backup now",
        "description": "Requests an on-demand backup by dropping a trigger file the db-backup container  polls for. System administrators only. Returns 202 (accepted); the backup runs  asynchronously in that container, not in this request.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/backups/route.ts)",
        "security": [],
        "responses": {
          "202": {
            "description": "Backup requested; it will run asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The backup service (trigger volume) is not mounted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/captcha-test": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminSettingsCaptchaTest",
        "summary": "Test the configured hCaptcha keys",
        "description": "Verifies an hCaptcha token against the configured secret key, so an admin can  confirm their hCaptcha keys actually work before turning captcha loose on real  logins. Returns `{ ok }`. Admin only; never returns the secret.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/captcha-test/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The hCaptcha response token from a solved widget"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether the token verified against the stored secret.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettings",
        "summary": "Get system settings",
        "description": "Returns the singleton system settings, falling back to defaults for any unset  field. The hCaptcha secret is never returned; only `hcaptchaSecretConfigured`  reports whether one is stored. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The effective system settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timezone": {
                      "type": "string"
                    },
                    "maxUploadSizeMb": {
                      "type": "integer"
                    },
                    "allowSignup": {
                      "type": "boolean"
                    },
                    "signupAllowedDomains": {
                      "type": "string",
                      "description": "Comma-separated email-domain allow-list; blank = any"
                    },
                    "sessionTimeoutMinutes": {
                      "type": "integer"
                    },
                    "submissionEvalTimeoutMs": {
                      "type": "integer"
                    },
                    "submissionEvalMaxMemoryMb": {
                      "type": "integer"
                    },
                    "submissionResubmitCooldownMs": {
                      "type": "integer"
                    },
                    "submissionMaxConcurrent": {
                      "type": "integer"
                    },
                    "submissionMaxAttempts": {
                      "type": "integer"
                    },
                    "submissionAnalyzerLimit": {
                      "type": "integer"
                    },
                    "loginMaxAttempts": {
                      "type": "integer"
                    },
                    "loginLockoutMinutes": {
                      "type": "integer"
                    },
                    "backupEnabled": {
                      "type": "boolean"
                    },
                    "backupHour": {
                      "type": "integer",
                      "description": "Hour of day (0-23) the daily backup runs"
                    },
                    "backupRetentionDays": {
                      "type": "integer"
                    },
                    "activityLogRetentionDays": {
                      "type": "integer"
                    },
                    "hcaptchaSiteKey": {
                      "type": "string"
                    },
                    "hcaptchaSecretConfigured": {
                      "type": "boolean",
                      "description": "Whether a secret is stored; the value is never returned"
                    },
                    "configuredUrl": {
                      "type": "string",
                      "description": "Read-only NEXTAUTH_URL (the app public address); set at the server level and not editable here"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "admin"
        ],
        "operationId": "putAdminSettings",
        "summary": "Update system settings",
        "description": "Updates the singleton system settings (upsert). Every field is optional, so a  partial payload only touches the fields it includes; numeric fields are clamped  to safe bounds and an invalid timezone is rejected. The hCaptcha secret is  write-only: send a non-empty `hcaptchaSecretKey` to set it, or  `hcaptchaSecretClear: true` to remove it. Changes are audited (never the secret  value). System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "timezone": {
                    "type": "string"
                  },
                  "maxUploadSizeMb": {
                    "type": "integer"
                  },
                  "allowSignup": {
                    "type": "boolean"
                  },
                  "sessionTimeoutMinutes": {
                    "type": "integer"
                  },
                  "submissionEvalTimeoutMs": {
                    "type": "integer"
                  },
                  "submissionEvalMaxMemoryMb": {
                    "type": "integer"
                  },
                  "submissionResubmitCooldownMs": {
                    "type": "integer"
                  },
                  "submissionMaxConcurrent": {
                    "type": "integer"
                  },
                  "submissionMaxAttempts": {
                    "type": "integer"
                  },
                  "submissionAnalyzerLimit": {
                    "type": "integer"
                  },
                  "loginMaxAttempts": {
                    "type": "integer"
                  },
                  "loginLockoutMinutes": {
                    "type": "integer"
                  },
                  "backupEnabled": {
                    "type": "boolean"
                  },
                  "backupHour": {
                    "type": "integer"
                  },
                  "backupRetentionDays": {
                    "type": "integer"
                  },
                  "activityLogRetentionDays": {
                    "type": "integer"
                  },
                  "hcaptchaSiteKey": {
                    "type": "string"
                  },
                  "hcaptchaSecretKey": {
                    "type": "string",
                    "description": "Write-only; a non-empty value sets the secret"
                  },
                  "hcaptchaSecretClear": {
                    "type": "boolean",
                    "description": "Set true to remove the stored secret"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated settings (same shape as GET)."
          },
          "400": {
            "description": "Invalid JSON body or invalid timezone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator (attempt is audited).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to persist the update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/tls/acme-progress": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsTlsAcmeProgress",
        "summary": "Get Let's Encrypt issuance progress",
        "description": "Live progress of an in-flight Let's Encrypt issuance. The admin UI polls this while  the (blocking) request runs so it can show step-by-step status instead of a spinner.  Admin only. Returns `{ phase: 'idle' }` when nothing has been recorded.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/tls/acme-progress/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The current issuance phase.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "phase": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string",
                      "nullable": true
                    },
                    "updatedAt": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/tls": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsTls",
        "summary": "Get TLS certificate status",
        "description": "Returns metadata about the currently installed TLS certificate and whether a  CSR is awaiting a signed cert. Admin only. Never returns key or PEM material.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/tls/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Certificate metadata and pending-CSR flag.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subject": {
                      "type": "string",
                      "nullable": true
                    },
                    "issuer": {
                      "type": "string",
                      "nullable": true
                    },
                    "validTo": {
                      "type": "string",
                      "nullable": true
                    },
                    "selfSigned": {
                      "type": "boolean",
                      "nullable": true
                    },
                    "pendingCsr": {
                      "type": "boolean"
                    },
                    "acme": {
                      "type": "object",
                      "description": "Let's Encrypt auto-renewal state (managed=false when not configured).",
                      "properties": {
                        "managed": {
                          "type": "boolean"
                        },
                        "domain": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "staging": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminSettingsTls",
        "summary": "Install or generate a TLS certificate",
        "description": "Performs a certificate operation, chosen by the `action` field. Admin only;  unauthorized-but-authenticated attempts are recorded as a security event. Cert  bodies and keys are accepted in the request but never echoed back or logged.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/tls/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "install",
                      "generate-csr",
                      "install-signed",
                      "self-signed",
                      "lets-encrypt",
                      "lets-encrypt-disable"
                    ],
                    "description": "install (default) = upload cert+key; generate-csr = create a CSR to be signed externally; install-signed = install the cert returned for a pending CSR; self-signed = generate a self-signed cert; lets-encrypt = obtain a free auto-renewing cert via ACME HTTP-01; lets-encrypt-disable = turn off auto-renewal (leaves the current cert in place)."
                  },
                  "cert": {
                    "type": "string",
                    "description": "PEM cert (install / install-signed)"
                  },
                  "key": {
                    "type": "string",
                    "description": "PEM private key (install)"
                  },
                  "chain": {
                    "type": "string",
                    "description": "Optional intermediate chain"
                  },
                  "commonName": {
                    "type": "string",
                    "description": "CSR/self-signed subject CN"
                  },
                  "organization": {
                    "type": "string"
                  },
                  "altNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "domain": {
                    "type": "string",
                    "description": "Public domain for lets-encrypt (must resolve to this server)"
                  },
                  "email": {
                    "type": "string",
                    "description": "Contact email for lets-encrypt"
                  },
                  "staging": {
                    "type": "boolean",
                    "description": "Use the Let's Encrypt staging CA for testing"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resulting certificate metadata (plus `csr` for generate-csr)."
          },
          "400": {
            "description": "Invalid JSON, or the certificate/key was rejected by validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The certificate operation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "admin"
        ],
        "operationId": "deleteAdminSettingsTls",
        "summary": "Reset TLS to a self-signed certificate",
        "description": "Removes the installed certificate and reverts to a self-signed one. Admin only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/tls/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Certificate reset; returns the new (self-signed) metadata."
          },
          "403": {
            "description": "Caller is not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/upgrade": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsUpgrade",
        "summary": "Get upgrade status and available versions",
        "description": "Reports the deployed version, the available curated releases, and the latest  progress of any in-flight upgrade. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/upgrade/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Current version, available releases, and updater status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "current": {
                      "type": "string"
                    },
                    "status": {
                      "type": "object",
                      "nullable": true
                    },
                    "versions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "manifestError": {
                      "type": "boolean"
                    },
                    "updaterAvailable": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminSettingsUpgrade",
        "summary": "Request an application upgrade or downgrade",
        "description": "Requests an application upgrade to a curated release by dropping a validated  request for the updater sidecar to perform. System administrators only. Returns  202; the swap, health check, and rollback happen asynchronously in the sidecar.  Upgrade to a curated release, or DOWNGRADE by restoring a recorded pre-upgrade  backup (which permanently discards everything created since it). Returns 202.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/upgrade/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "upgrade",
                      "downgrade",
                      "self-update",
                      "delete-restore-point"
                    ]
                  },
                  "tag": {
                    "type": "string"
                  },
                  "restorePoint": {
                    "type": "string"
                  },
                  "force": {
                    "type": "boolean",
                    "description": "Downgrade only: proceed without a confirmed pre-downgrade safety backup."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Requested; it will run asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid tag/restore point, an unknown release, or the current version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The release list or the updater service is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/settings/upgrade/stream": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminSettingsUpgradeStream",
        "summary": "Stream live upgrade progress (Server-Sent Events)",
        "description": "Server-Sent Events stream of an in-flight upgrade: the coarse phase from  status.json plus new lines from the updater's progress.log, pushed as they appear  so the Updates UI shows real-time progress without polling or a manual refresh.  Closes when the upgrade reaches a terminal phase (or after a time cap). System  administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/settings/upgrade/stream/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "An event stream (text/event-stream) of status + log events."
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/database": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusDatabase",
        "summary": "Database status",
        "description": "Database tab: reachability, engine details, per-engine stats (Postgres or  SQLite), and the last migration. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/database/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "DB health, details, and engine stats."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/docker": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusDocker",
        "summary": "Docker/container status",
        "description": "Docker tab: container detection (cgroups, /.dockerenv, env hints) and container  id / hostname when running inside a container. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/docker/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Container info, or { docker: null } when not containerized."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/files": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusFiles",
        "summary": "Abandoned-file report",
        "description": "Files tab: report of orphaned uploads, files on disk that no DB row  references, by category, with up to 50 samples. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/files/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Orphaned-upload counts and samples."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "admin"
        ],
        "operationId": "deleteAdminStatusFiles",
        "summary": "Delete an orphaned upload",
        "description": "Deletes a single orphaned upload. Guards on every axis (known category,  separator-free name, still unreferenced, path stays inside the category  folder). System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/files/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "category",
                  "fileName"
                ],
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "solutions",
                      "submissions",
                      "pfps",
                      "problems"
                    ]
                  },
                  "fileName": {
                    "type": "string",
                    "description": "Bare filename, no path separators"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File deleted."
          },
          "400": {
            "description": "Unknown category, unsafe filename, or path outside the folder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found on disk.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A DB row still references this file; refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/network": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusNetwork",
        "summary": "Network status (DNS/TLS/latency/error-rate)",
        "description": "Network tab: DNS resolution for the DB and auth hosts, auth-endpoint latency +  TLS certificate expiry, DB round-trip latency and connection count, and recent  error-rate ratios. DNS/TLS are TTL-cached. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/network/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Upstream connectivity probes and error-rate summaries."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/rate-limits/clear": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminStatusRateLimitsClear",
        "summary": "Clear one IP address's rate limit (admin)",
        "description": "Lifts one IP's rate-limit restriction ahead of its expiry, so an administrator can  let a locked-out classroom or office back in without waiting it out. System  administrators only.   Not idempotent by design: clearing an address that is not currently restricted  returns 404 rather than a silent success, so the caller learns the restriction had  already lapsed (or landed on another instance) instead of assuming it acted.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/rate-limits/clear/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scope",
                  "ip"
                ],
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "login:ip",
                      "signup:ip",
                      "check-email:ip"
                    ],
                    "description": "Which limit to clear"
                  },
                  "ip": {
                    "type": "string",
                    "description": "The restricted address"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The restriction was cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid scope/ip.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That address is not currently restricted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Clearing the restriction failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/rate-limits": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusRateLimits",
        "summary": "Currently rate-limited IP addresses",
        "description": "Rate Limits tab: every client IP currently blocked or under a captcha-challenge  cooldown, with why it was restricted, since when, how hard it is still knocking,  when the restriction lifts on its own, and what is known about the address itself  (where it sits in the address space, its reverse-DNS name, and whether AFCT's own  activity log has seen it before).   The rate limiter keeps its buckets in process memory, so this reports the state of  the instance that serves the request and resets when the app restarts. It exposes  visitors' IP addresses, so system administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/rate-limits/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The IP addresses currently restricted on this instance."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/server": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusServer",
        "summary": "Server status (host + process + software)",
        "description": "Server tab: host + process metrics (uptime, CPU/memory/disk-IO sample, OS, IP  addresses) and software versions (Node/Next/Java/evaluator + build metadata).  System administrators only. Tool versions are TTL-cached server-side.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/server/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Server metrics and software versions."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/sessions": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusSessions",
        "summary": "Active session status",
        "description": "Session tab: recent active sessions (last 24h, deduped) and rolling 5/15/60m  counts, from the audit log. Includes other users' session PII (emails, IPs), so  system administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/sessions/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Active sessions and rolling counts."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/status/summary": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminStatusSummary",
        "summary": "Status summary (top cards)",
        "description": "Summary cards at the top of the status dashboard: DB reachability/provider,  uptime, process CPU/memory, DB table count + size, 24h session counts, and this  probe's own latency. Fast by design so it renders immediately while the per-tab  detail loads lazily. System administrators only.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/status/summary/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Cross-cutting summary numbers for the status cards."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/submissions": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminSubmissions",
        "summary": "List submissions for problems (admin)",
        "description": "Returns every submission across a set of problems, flattened for the admin  grading view: student, course, assignment/problem titles, status, and the  recorded grade (joined from AssignmentProblemGrade). System administrators only.  Takes the problem ids in the body rather than the query string since the list  can be long.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/submissions/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "problemIds"
                ],
                "properties": {
                  "problemIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Flattened submissions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "400": {
            "description": "problemIds missing or empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/unlock-account": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminUnlockAccount",
        "summary": "Unlock a locked-out account (admin)",
        "description": "Clears an account's auto-expiring login lock ahead of its expiry, so a user locked  out by failed attempts can sign in again immediately. System administrators only.  Idempotent: clearing an already-unlocked account is a no-op success.   This only lifts the login gate; it does not reset the in-memory rate-limiter counters  for the current instance, so a fresh burst of bad passwords can re-lock the account.  That is intentional - the durable lock is the thing an admin manages.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/unlock-account/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "The user to unlock"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account unlocked (or already unlocked).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "wasLocked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing userId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Target user not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unlock failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/users/bulk": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminUsersBulk",
        "summary": "Bulk-create users",
        "description": "Bulk-creates user accounts from parsed spreadsheet rows (the CSV import flow).  System administrators only. Accounts are created with no global role. Each row  is validated independently: a bad row  is collected in `failed` with a reason rather than aborting the batch, so the  response always reports per-row created/failed outcomes. Duplicate emails are  caught both within the batch and against existing users. `temporaryPasswords`  forces a reset at first login.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/users/bulk/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rows"
                ],
                "properties": {
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "rowNumber": {
                          "type": "integer",
                          "description": "Source spreadsheet row, for error reporting"
                        },
                        "firstName": {
                          "type": "string"
                        },
                        "lastName": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "password": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "temporaryPasswords": {
                    "type": "boolean",
                    "description": "Force a password change at first login"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-row outcome (a summary plus created and failed lists).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "created": {
                          "type": "integer"
                        },
                        "failed": {
                          "type": "integer"
                        }
                      }
                    },
                    "created": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "failed": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No rows provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "System administrators only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/users/list": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminUsersList",
        "summary": "List users (paginated)",
        "description": "One page of the admin users table. Search, filters, sort, and pagination all run  server-side. System administrators only; read-only (no audit-log side effect, unlike  `GET /api/admin/users`).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/users/list/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Match on first name, last name, or email",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "field",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "firstName",
                "lastName",
                "email"
              ]
            }
          },
          {
            "name": "admin",
            "in": "query",
            "description": "Repeatable true/false",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  true,
                  false
                ]
              }
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Repeatable",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "active",
                  "inactive"
                ]
              }
            }
          },
          {
            "name": "lock",
            "in": "query",
            "description": "Repeatable",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "locked",
                  "unlocked"
                ]
              }
            }
          },
          {
            "name": "temp",
            "in": "query",
            "description": "Repeatable true/false",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  true,
                  false
                ]
              }
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "firstName",
                "lastName",
                "email",
                "isAdmin",
                "inactive",
                "temporaryPassword",
                "createdAt",
                "lastLogin"
              ]
            }
          },
          {
            "name": "sortDir",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    },
                    "totalPages": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin/users": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "getAdminUsers",
        "summary": "List users",
        "description": "Lists users for the admin-facing users table. System administrators only; the  access itself is audited.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/users/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "403": {
            "description": "System administrators only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "postAdminUsers",
        "summary": "Create a user",
        "description": "Creates a single user directly (admin-provisioned account), unlike self-service  signup. System administrators only. Validates email, password strength, and  timezone, and rejects a duplicate email. The account is created with no global  role; admin rights are granted separately via the isAdmin flag.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/admin/users/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "firstName",
                  "lastName",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string",
                    "description": "Must meet the strength policy"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "Defaults to the system timezone"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created user."
          },
          "400": {
            "description": "Missing fields, invalid email, weak password, or invalid timezone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "System administrators only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Email already in use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/assignments/{id}/problems": {
      "get": {
        "tags": [
          "assignments"
        ],
        "operationId": "getAssignmentsByIdProblems",
        "summary": "List an assignment's problems",
        "description": "Lists an assignment's problems, tagged with whether the caller has solved each  (a correct submission) and their grade. All problems are visible. Course faculty  or a system admin (TAs excluded).\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/assignments/[id]/problems/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Assignment id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The visible problems with solved/grade flags.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing assignment id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in, or not course faculty / a system admin (TAs excluded).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/check-email": {
      "get": {
        "tags": [
          "auth"
        ],
        "operationId": "getAuthCheckEmail",
        "summary": "Check whether an email is registered",
        "description": "Reports whether an email is already registered, so the signup form can warn  before submitting. Unauthenticated by design; it therefore leaks account  existence, which is an accepted trade-off for signup UX, but it is IP rate-limited  so it can't be used to bulk-enumerate accounts, and it only ever returns a boolean.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/auth/check-email/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether a user with that email exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "exists": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The email query parameter is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many checks from this IP; retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/login-check": {
      "post": {
        "tags": [
          "auth"
        ],
        "operationId": "postAuthLoginCheck",
        "summary": "Peek login rate-limit status",
        "description": "Read-only login rate-limit status. The login form calls this after a failed  `signIn` to classify the failure, because NextAuth (Auth.js v5) reports any  `authorize` error only as a generic `CredentialsSignin`, so the client cannot  otherwise tell a captcha challenge or a temporary block apart from bad credentials.  This does NOT count an attempt (the credentials `authorize` path is the single  source of truth); it only reports the flags that path already set. Public.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/auth/login-check/route.ts)",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "The account being signed into (for the per-account bucket)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The current rate-limit status for this IP + account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "challenge",
                        "blocked"
                      ]
                    },
                    "retryAfterMs": {
                      "type": "integer",
                      "description": "Milliseconds until the challenge/block clears (0 when ok)"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/signup": {
      "post": {
        "tags": [
          "auth"
        ],
        "operationId": "postAuthSignup",
        "summary": "Register a new account",
        "description": "Self-service account registration. New accounts are created with no elevated  privileges; access is granted later through the staff-only user-management routes.  Gated by the `allowSignup` system setting, and protected by a tiered rate  limiter: repeated attempts escalate from silent friction, to a captcha challenge  (428), to an outright block (429). The password must satisfy the strength policy.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/auth/signup/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "firstName",
                  "lastName",
                  "email",
                  "password"
                ],
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string",
                    "description": "Must meet the strength policy"
                  },
                  "interactionMs": {
                    "type": "integer",
                    "description": "Time spent on the form; feeds bot-friction heuristics"
                  },
                  "captchaToken": {
                    "type": "string",
                    "description": "Required only when the rate limiter issues a challenge"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "userId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields, invalid email, or weak password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Signup is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Email already registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "Rate limiter requires a captcha challenge; retry with captchaToken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts; retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/auth/login": {
      "post": {
        "tags": [
          "client"
        ],
        "operationId": "postClientV1AuthLogin",
        "summary": "Client login (issue a bearer token)",
        "description": "Native-client login. Verifies email + password through the same shared path as  the browser login (rate limiting, account lockout, bot friction, bcrypt, security  logging) and, on success, issues a **bearer token** the client sends on every  later request as `Authorization: Bearer <token>`. Unlike the browser flow there's  no cookie, no CSRF, and no idle-timeout. The rate limiter's captcha \"challenge\"  (which a native client can't solve) is reported as a 429 back-off.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/auth/login/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  },
                  "deviceName": {
                    "type": "string",
                    "description": "Optional label to identify this token"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A bearer token and the signed-in user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string"
                    },
                    "user": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid email or password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts; retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/auth/logout": {
      "post": {
        "tags": [
          "client"
        ],
        "operationId": "postClientV1AuthLogout",
        "summary": "Client logout (revoke the current token)",
        "description": "Revokes the bearer token used to make this request, so it can no longer  authenticate. Idempotent.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/auth/logout/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Token revoked."
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/auth/me": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1AuthMe",
        "summary": "Check the current token (whoami)",
        "description": "Whoami / token check. A cheap endpoint the client can call to confirm its bearer  token is still valid: `200` with the user when the token is good, `401` when it's  missing, expired, or revoked. (Also renews the sliding expiry, like any  authenticated call.)\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/auth/me/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The token is valid; returns the signed-in user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object"
                    },
                    "expiresAt": {
                      "type": "string",
                      "nullable": true,
                      "description": "When the (sliding) token expires"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired, or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/courses/{courseId}/assignments": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1CoursesByCourseIdAssignments",
        "summary": "List a course's assignments + problems (client)",
        "description": "A course's assignments and their problems, scoped by the caller's role in the  course (which they must be able to access, else 404):    - Admin / course staff (Faculty, TA): every assignment, published or not.    - Student: only published assignments they are assigned, and only once past the      assignment's available (\"unlock\") date if one is set. Pre-unlock assignments      are withheld entirely.  Each assignment also reports whether it is an individual or group assignment, the  caller's group name when it is a group assignment they belong to, and whether late  submissions are accepted. The answer-key file is never included.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/courses/[courseId]/assignments/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Assignments, each with its problems.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assignments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found or not accessible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/courses": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1Courses",
        "summary": "List my courses (client)",
        "description": "The signed-in user's courses (slim shape for the client), scoped to the token's  user. Visibility is per the viewer's role in each course, and never lists a  deleted or archived course:    - Admin: every non-archived course (published or not), enrolled or not.    - Faculty/TA: their non-archived courses (published or not) where they are staff.    - Student: their published non-archived courses that are currently within the      course's start/end date range (a submission tool only lists courses you can      submit to now).  A user who is staff in one course and a student in another is judged per course.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/courses/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "The caller's courses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "courses": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/submissions/{submissionId}": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1SubmissionsBySubmissionId",
        "summary": "Get a submission's result (client)",
        "description": "The result of one submission, for polling after a submit. Returns the queue  `status` (PENDING/PROCESSING/COMPLETED/FAILED) and, once evaluated, whether it was  `correct`, the `grade`, and the `feedback` (the witness / counterexample string).  A caller may read their own submission; staff may read anyone's in their course.  Anything else is masked as 404.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/submissions/[submissionId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "submissionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The submission's status and (when done) result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "correct": {
                      "type": "boolean",
                      "nullable": true
                    },
                    "grade": {
                      "type": "number",
                      "nullable": true
                    },
                    "feedback": {
                      "type": "string",
                      "nullable": true,
                      "description": "The witness / counterexample"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Submission not found or not visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/submissions": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1Submissions",
        "summary": "List my submissions for a problem (client)",
        "description": "The caller's own submission history for one problem (attempt list), newest first,  so the client can show past attempts and drill into any one's result via  `GET /submissions/{id}`. Scoped to the token's user, so it never exposes anyone  else's work.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/submissions/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "assignmentId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "problemId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's attempts for the problem, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "submissions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing assignmentId or problemId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled in the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found, unpublished, or the problem is not linked to it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "client"
        ],
        "operationId": "postClientV1Submissions",
        "summary": "Submit a solution (client)",
        "description": "Submit a solution file (client). Same multipart body, validation, caps, cooldown,  late policy, storage, and queueing as the web `/api/submissions` (it runs the same  `createSubmission` service) but authenticated by a bearer token. Returns 202 with  the new submission's id + status.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/submissions/route.ts)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "assignmentId",
                  "problemId"
                ],
                "properties": {
                  "assignmentId": {
                    "type": "string"
                  },
                  "problemId": {
                    "type": "string"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The solution file (XML)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Submission accepted and queued (status PENDING)."
          },
          "400": {
            "description": "Missing fields, unlinked problem, or invalid file structure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled, or the late policy rejected it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Per-problem submission limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "File exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Resubmit cooldown in effect (see Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/client/v1/tree": {
      "get": {
        "tags": [
          "client"
        ],
        "operationId": "getClientV1Tree",
        "summary": "My full course tree (client)",
        "description": "The caller's entire course tree in one call: every visible course with its assignments  and their problems, resolved for this user. Lets the native client load once and filter  locally (upcoming assignments, unsolved problems) instead of fetching per course.   Visibility matches the per-course endpoints: admins and course staff (Faculty, TA) see  every assignment, published or not; students see only published assignments assigned to  them that have unlocked. Each problem includes a derived `solved` (full marks earned).  Answer-key files are never included.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/client/v1/tree/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Every visible course, each with its assignments and problems.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "serverTime": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "courses": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/comments": {
      "post": {
        "tags": [
          "comments"
        ],
        "operationId": "postComments",
        "summary": "Create a comment",
        "description": "Creates a comment on an assignment problem, optionally scoped to a particular  student's thread (`studentId`). The author must be an enrolled member of the course  (any role) or a system admin; a system admin who isn't on the roster comments with no  roster entry (`rosterId` null). Both the problem and any named student must belong to the course.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/comments/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "assignmentId",
                  "problemId"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 5000
                  },
                  "assignmentId": {
                    "type": "string"
                  },
                  "problemId": {
                    "type": "string"
                  },
                  "studentId": {
                    "type": "string",
                    "description": "The student this thread is about"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created comment with its author."
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Author is not an enrolled member of the course or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment, problem, or named student not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "comments"
        ],
        "operationId": "deleteComments",
        "summary": "Delete a comment",
        "description": "Deletes a comment by id. Comments are immutable to students: **only course staff  (faculty or TAs) or a system admin may delete**, including deleting their own.  A student cannot delete a comment they authored.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/comments/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "commentId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comment deleted."
          },
          "400": {
            "description": "Missing commentId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Comment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/activity": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdActivity",
        "summary": "Get a course's activity feed",
        "description": "Returns a paginated activity feed for one course: course/assignment/problem/  submission activity plus member logins. Course-content activity by admins (even if  not enrolled) and enrolled staff (Faculty/TA) shows any time (so an admin creating  or editing a problem before the term is included), while other members' content and  all member logins are clipped to the course's start/end dates. Admin logins are never  shown (only their course edits). Staff-only to read (see access gate below).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/activity/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of activity entries with a total count.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "activities": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled in the course and not a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/archive": {
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdArchive",
        "summary": "Archive or unarchive a course",
        "description": "Toggles a course's archived state. **Both archiving and un-archiving are  admin-only**: freezing a course (or reopening a frozen one to edits) is a  privileged action. Archiving also runs a safety check (canArchiveCourse) using  the course's stored dates rather than any client value, to avoid timezone drift  deciding whether a course has really ended.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/archive/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "isArchived"
                ],
                "properties": {
                  "isArchived": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated course (id, name, code, isArchived, updatedAt)."
          },
          "400": {
            "description": "isArchived must be a boolean.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin, or archiving is blocked by the safety check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/assignees": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidAssignees",
        "summary": "List an assignment's assignees",
        "description": "Lists an assignment's audience (its AssignmentAssignee rows). Empty when the assignment  is assigned to everyone. Course staff (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/assignees/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The assignment's assignee rows."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesByIdAssignmentsByAidAssignees",
        "summary": "Replace an assignment's audience",
        "description": "Replaces an assignment's audience. Course staff (faculty or TAs) or a system admin.  `assignedToEveryone` true clears the explicit list; false assigns only the given targets  (students for an individual assignment, groups for a group assignment). Overrides for  anyone no longer assigned are dropped in the same transaction.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/assignees/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "assignedToEveryone"
                ],
                "properties": {
                  "assignedToEveryone": {
                    "type": "boolean"
                  },
                  "assignees": {
                    "type": "array",
                    "description": "Required when assignedToEveryone is false; each item is one student (userId) or group (groupId)",
                    "items": {
                      "type": "object",
                      "properties": {
                        "userId": {
                          "type": "string"
                        },
                        "groupId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated assignment."
          },
          "400": {
            "description": "Invalid audience for this assignment's type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/duplicate": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidDuplicate",
        "summary": "Duplicate an assignment",
        "description": "Duplicate an assignment within the same course. The title/description come from the  request; the type (groupSetId), audience (AssignmentAssignee), schedule, and date  exceptions (AssignmentOverride) are copied verbatim from the source and are editable  afterward. The copy is always created unpublished. Submissions and grades are never  copied.   Problems are handled by `problemMode`:    - none      : the copy has no problems.    - link      : the copy shares the source's Problem records (editing one edits both).    - duplicate : each problem is copied to a new Problem (with its own solution file).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/duplicate/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "problemMode"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  },
                  "problemMode": {
                    "type": "string",
                    "enum": [
                      "none",
                      "link",
                      "duplicate"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The newly created (unpublished) assignment."
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/overrides/{oid}": {
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdAssignmentsByAidOverridesByOid",
        "summary": "Update an assignment due-date override",
        "description": "Updates a per-student due-date override. Course staff (faculty or TAs) or a system  admin. Omitted fields keep their current value; null inherits the base.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/overrides/[oid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "oid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "unlockAt": {
                    "type": "string",
                    "nullable": true
                  },
                  "dueDate": {
                    "type": "string",
                    "nullable": true
                  },
                  "lateCutoff": {
                    "type": "string",
                    "nullable": true
                  },
                  "allowLateSubmissions": {
                    "type": "boolean",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated override."
          },
          "400": {
            "description": "Invalid window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Override not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdAssignmentsByAidOverridesByOid",
        "summary": "Delete an assignment due-date override",
        "description": "Deletes a per-student due-date override (the student falls back to the base dates).  Course staff (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/overrides/[oid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "oid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Override not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/overrides": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidOverrides",
        "summary": "List an assignment's due-date overrides",
        "description": "Lists the per-student due-date overrides for an assignment. Course staff (faculty or  TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/overrides/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The assignment's overrides."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidOverrides",
        "summary": "Create an assignment due-date override",
        "description": "Creates a per-student due-date override. Course staff (faculty or TAs) or a system  admin. The target must be a student enrolled in the course. Dates are interpreted in  the course's timezone; the effective window (override values falling back to the base)  must stay ordered.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/overrides/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "Student target (exactly one of userId or groupId)"
                  },
                  "groupId": {
                    "type": "string",
                    "description": "Group target (a StudentGroup in the assignment's group set)"
                  },
                  "unlockAt": {
                    "type": "string",
                    "nullable": true
                  },
                  "dueDate": {
                    "type": "string",
                    "nullable": true
                  },
                  "lateCutoff": {
                    "type": "string",
                    "nullable": true
                  },
                  "allowLateSubmissions": {
                    "type": "boolean",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created override."
          },
          "400": {
            "description": "Invalid target or window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "An override already exists for this student.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problem-grades/{studentId}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidProblemGradesByStudentId",
        "summary": "Get a student's problem grades for an assignment",
        "description": "Returns a student's per-problem grades and feedback for one assignment, keyed by  problem id. A student may read their own; staff may read anyone's. Responds 204  when nothing has been graded yet.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problem-grades/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A map of problemId → { grade, feedback, updatedAt }.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "204": {
            "description": "No grades recorded yet."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not the student in question and not staff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidProblemGradesByStudentId",
        "summary": "Batch set/clear a student's problem grades for an assignment",
        "description": "Batch-saves this student's problem grades for the assignment in a single request;  the write counterpart to the GET above (co-located as the same resource). The body  maps problemId → grade (a number within [0, maxPoints], or null to clear). Course  staff (faculty or TAs) or a system admin. Only problems whose grade actually changed  are written: a null for a graded problem deletes it, a number upserts it (existing  feedback is left untouched), and unchanged problems are skipped. Every applied change  is audited with its previous value, mirroring the single-problem grade route.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problem-grades/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grades"
                ],
                "properties": {
                  "grades": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number",
                      "nullable": true
                    },
                    "description": "Map of problemId to grade (0..maxPoints) or null to clear."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch applied; returns the number of problems changed."
          },
          "400": {
            "description": "Bad body, unknown problem id, or a grade out of range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problem-grades/summary": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidProblemGradesSummary",
        "summary": "Get an assignment's grading-completion summary",
        "description": "Per-student completion summary for one assignment: maps each student to whether  every problem in the assignment has been graded (used to flag fully-graded  students in the grading UI). Course staff (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problem-grades/summary/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A map of studentId → fully-graded boolean (empty object if the assignment has no problems).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problems/{pid}/grade/{studentId}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidProblemsByPidGradeByStudentId",
        "summary": "Get a single problem grade",
        "description": "Reads one student's grade and feedback for a specific problem within an  assignment. The student themselves, course staff, or a system admin. Returns nulls  (not 404) when the problem exists but hasn't been graded.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/grade/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grade, feedback, and updatedAt (grade/feedback null if ungraded)."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not the student themselves, course staff, or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this assignment/course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidProblemsByPidGradeByStudentId",
        "summary": "Set or clear a problem grade",
        "description": "Sets or clears a student's grade (and optional feedback) for one problem. Course  staff (faculty or TAs) or a system admin. A numeric grade must be within [0, maxPoints]; sending  a null grade deletes the record. Every change is audited with the previous value.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/grade/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "grade": {
                    "type": "number",
                    "nullable": true,
                    "description": "0..maxPoints, or null to clear"
                  },
                  "feedback": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved (or cleared) grade and feedback."
          },
          "400": {
            "description": "Grade not a number/null, or out of range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this assignment/course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problems/{pid}/grants/{gid}": {
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdAssignmentsByAidProblemsByPidGrantsByGid",
        "summary": "Revoke an extra-submission grant",
        "description": "Revokes an extra-submission grant. Submissions already made stay untouched; the  target's cap simply returns to the shared value. Course staff (faculty or TAs) or a  system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/grants/[gid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "gid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant was removed."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Grant not found for this problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Course is archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problems/{pid}/grants": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidProblemsByPidGrants",
        "summary": "List a problem's extra-submission grants",
        "description": "Lists the extra-submission grants for one assignment problem. Course staff (faculty or  TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/grants/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The problem's grants (newest first)."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this assignment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidProblemsByPidGrants",
        "summary": "Grant extra submissions to a student or group",
        "description": "Grants extra submissions to one student or group on this problem, on top of the  problem's shared cap. Repeat grants to the same target accumulate onto one row. Course  staff (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/grants/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "extraSubmissions"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "Target student (exactly one of userId/groupId)."
                  },
                  "groupId": {
                    "type": "string",
                    "description": "Target group (exactly one of userId/groupId)."
                  },
                  "extraSubmissions": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  },
                  "reason": {
                    "type": "string",
                    "description": "Optional note shown to staff."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The grant row (with the accumulated total)."
          },
          "400": {
            "description": "Invalid target or an unlimited problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this assignment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Course archived or a concurrent grant conflicted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problems/{pid}": {
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesByIdAssignmentsByAidProblemsByPid",
        "summary": "Update an assignment problem's settings",
        "description": "Updates the per-assignment settings for one problem: its point value, submission  cap, and whether the autograder runs. Course staff (faculty or TAs) or a system  admin. The problem  must already be linked to the assignment, and the assignment must belong to the  course in the path.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/[pid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "maxPoints",
                  "maxSubmissions",
                  "autograderEnabled"
                ],
                "properties": {
                  "maxPoints": {
                    "type": "number",
                    "minimum": 0
                  },
                  "maxSubmissions": {
                    "type": "integer",
                    "description": "-1 for unlimited, else >= 1"
                  },
                  "autograderEnabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated assignment-problem settings."
          },
          "400": {
            "description": "Invalid JSON or settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The problem isn't linked to this assignment/course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/problems": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsByAidProblems",
        "summary": "Add problems to an assignment",
        "description": "Attaches problems to an assignment with per-problem settings (points, submission  cap, autograder). Course staff (faculty or TAs) or a system admin. Adds only problems not already  linked; existing links, especially those with submissions, are preserved and  reported back. Only problems belonging to this course are accepted.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "problemIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "problemSettings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "problemId",
                        "maxPoints",
                        "maxSubmissions",
                        "autograderEnabled"
                      ],
                      "properties": {
                        "problemId": {
                          "type": "string"
                        },
                        "maxPoints": {
                          "type": "number",
                          "minimum": 0
                        },
                        "maxSubmissions": {
                          "type": "integer",
                          "description": "-1 for unlimited, else >= 1"
                        },
                        "autograderEnabled": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assignment's problem list plus a summary of what changed."
          },
          "400": {
            "description": "Empty/invalid body or invalid problemSettings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdAssignmentsByAidProblems",
        "summary": "Remove a problem from an assignment",
        "description": "Detaches a problem from an assignment, leaving the problem itself intact in the  course. Course staff (faculty or TAs) or a system admin. Both the assignment and  the problem must belong to the course in the path. The problem id travels in the  request body.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/problems/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "problemId"
                ],
                "properties": {
                  "problemId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assignment's remaining problems."
          },
          "400": {
            "description": "Missing problemId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment or problem not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/review-data/{studentId}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidReviewDataByStudentId",
        "summary": "Get a student's review data for an assignment",
        "description": "Assembles the grading/review view for one student on one assignment: their  submissions (grouped by problem, with evaluation output), the comments about  them, and their per-problem grades. Falls back gracefully if the optional  `evaluationRaw` column is absent.   Access: the student themselves, course staff, or a system admin (`studentId` must  be the caller's id unless they are course staff or a system admin). Course  membership is also required, except for global admins.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/review-data/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Submissions (by problem), comments, and problem grades for the student.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "submissions": {
                      "type": "object"
                    },
                    "comments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "problemGrades": {
                      "type": "object"
                    },
                    "isGroup": {
                      "type": "boolean",
                      "description": "Whether the student submits this assignment as a group."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requesting another student's data without being course staff or a system admin, or not an enrolled member of the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found for this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAid",
        "summary": "Get a course assignment",
        "description": "Returns the assignment with its problems. Staff/admins also get the course roster in the full view; non-staff members see published assignments only (unpublished are masked as 404) and no roster.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "view",
            "in": "query",
            "description": "\"full\" (default) includes the roster for staff; any other value omits it.",
            "schema": {
              "type": "string",
              "default": "full"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The assignment with problems (and, for staff in full view, the roster)."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an enrolled member of the course and not a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course, or not visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesByIdAssignmentsByAid",
        "summary": "Update a course assignment (full)",
        "description": "Full update of an assignment. Course staff (faculty or TAs) or a system admin.  Guards protect data integrity: an assignment can't be unpublished once it has  submissions or grades, and its group mode can't change after any submission exists.  Late-submission rules are validated the same way as on create.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string"
                  },
                  "unlockAt": {
                    "type": "string",
                    "nullable": true,
                    "description": "Available-from date; null clears it"
                  },
                  "allowLateSubmissions": {
                    "type": "boolean"
                  },
                  "lateCutoff": {
                    "type": "string",
                    "nullable": true
                  },
                  "isPublished": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated assignment."
          },
          "400": {
            "description": "Inconsistent late-submission window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin, or a state guard blocked the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdAssignmentsByAid",
        "summary": "Update a course assignment (partial)",
        "description": "Partial update of an assignment: only the fields present in the body are changed.  Course staff (faculty or TAs) or a system admin, with the same unpublish guard and  late-window validation as the full update.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string"
                  },
                  "unlockAt": {
                    "type": "string",
                    "nullable": true,
                    "description": "Available-from date; null clears it"
                  },
                  "allowLateSubmissions": {
                    "type": "boolean"
                  },
                  "lateCutoff": {
                    "type": "string",
                    "nullable": true
                  },
                  "isPublished": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated assignment."
          },
          "400": {
            "description": "Inconsistent late-submission window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin, or a state guard blocked the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdAssignmentsByAid",
        "summary": "Delete a course assignment",
        "description": "Deletes an assignment, but only when it's safe: no submissions and no comments. Its  problem links are cleared first, then the assignment is removed. Course staff  (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Assignment deleted."
          },
          "400": {
            "description": "Submissions or comments exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/statistics": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidStatistics",
        "summary": "Get an assignment's analytics",
        "description": "Aggregate analytics for one assignment: score histogram, per-problem box plots, and  submission-status breakdown, measured in students (individual) or groups (group  assignment). Course staff (faculty or TAs) or a system admin only. These are aggregate  student-performance figures, a FERPA-relevant read, so the access is audited (throttled).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/statistics/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Histogram, box plots, and status breakdown for the assignment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "unit": {
                      "type": "string",
                      "enum": [
                        "student",
                        "group"
                      ]
                    },
                    "participantCount": {
                      "type": "integer"
                    },
                    "exceptionCount": {
                      "type": "integer"
                    },
                    "histogram": {
                      "type": "object"
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "assignmentTitle": {
                      "type": "string"
                    },
                    "baseDueDate": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/student-context": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidStudentContext",
        "summary": "Get my context for an assignment",
        "description": "Everything the caller needs to see their own work on an assignment, grouped by  problem: their submissions, the comments addressed to them, and their per-problem  and overall grades. Requires enrollment in the course; students can't see it  until the assignment is published. Scoped entirely to the caller's own data.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/student-context/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Course id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "description": "Assignment id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's submissions, comments, and grades for the assignment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assignmentGrade": {
                      "type": "number",
                      "nullable": true
                    },
                    "problemGrades": {
                      "type": "object"
                    },
                    "submissionCount": {
                      "type": "integer"
                    },
                    "submissionsByProblem": {
                      "type": "object"
                    },
                    "commentsByProblem": {
                      "type": "object"
                    },
                    "problemLimits": {
                      "type": "object",
                      "description": "Per-problem effective submission cap for the caller (base plus any grants); max null means unlimited."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not enrolled in the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course, or unpublished (for students).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/student-group/{studentId}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidStudentGroupByStudentId",
        "summary": "Get a student's group membership for an assignment",
        "description": "Whether a student submits this assignment individually or as a group, the group's name +  groupmates (for a group), and the student's EFFECTIVE schedule (their own or their group's  date override resolved against the base). Drives the per-student due/late line and the  Individual / Group indicator on the staff Submissions view. Course staff or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/student-group/[studentId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "studentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The student's individual/group status for the assignment."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/submissions/{sid}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignmentsByAidSubmissionsBySid",
        "summary": "Get a student's submissions for an assignment",
        "description": "Returns a student's submissions for an assignment, grouped by problem and each  annotated with that problem's metadata (falls back gracefully if the optional  `evaluationRaw` column is absent). The `[sid]` segment is the student id.   Access: the student themselves, course staff, or a system admin (`sid` must be the  caller's id unless they are course staff or a system admin). Course membership is  also required, except for global admins.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/submissions/[sid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "description": "Student id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Submissions grouped by problem.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Requesting another student's submissions without being course staff or a system admin, or not an enrolled member of the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found, or it has no linked problems.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/{aid}/type": {
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesByIdAssignmentsByAidType",
        "summary": "Change an assignment's individual/group type",
        "description": "Changes an assignment's individual/group type. Course staff (faculty or TAs) or a system  admin. `groupSetId: null` makes it individual; a set id makes it a group assignment tied  to that set. Because assignees and date overrides reference the old type's targets,  switching resets the audience to everyone and clears all assignees + overrides in one  transaction (staff rebuild them on the Assign To tab).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/[aid]/type/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "aid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "groupSetId"
                ],
                "properties": {
                  "groupSetId": {
                    "type": "string",
                    "nullable": true,
                    "description": "Null for individual; a group set id for group"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated assignment."
          },
          "400": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments/import": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignmentsImport",
        "summary": "Import an assignment from another course",
        "description": "Imports an assignment from ANOTHER course the caller can manage into this course.  Unlike duplicate, audience, group set, and date exceptions are not carried across  (they reference records scoped to the source course); the import always lands as an  unpublished, individual, assigned-to-everyone assignment. The schedule (due date,  available-from, late settings) is copied from the source as a starting point and is  editable afterward.   Problems are handled by `problemMode`:    - none : the imported assignment has no problems.    - copy : each problem is copied into THIS course (a new Problem with its own             solution file); there is no \"link\" mode because problems are course-scoped.   Permission is tiered: the wrapper gates the destination course (manage), and the  caller must also be able to manage the source course.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/import/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Destination course id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sourceCourseId",
                  "sourceAssignmentId",
                  "title",
                  "problemMode"
                ],
                "properties": {
                  "sourceCourseId": {
                    "type": "string"
                  },
                  "sourceAssignmentId": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  },
                  "problemMode": {
                    "type": "string",
                    "enum": [
                      "none",
                      "copy"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The newly created (unpublished) assignment."
          },
          "400": {
            "description": "Invalid body, or the source is the destination course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller cannot manage the destination or the source course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source assignment not found in the source course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/assignments": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdAssignments",
        "summary": "List a course's assignments",
        "description": "Lists a course's assignments with each one's total and max grade (summed across  its problems). Published only by default; pass `includeUnpublished=1` to also  return drafts (used by the staff assignment switcher). Course staff or a system  admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeUnpublished",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true, include unpublished (draft) assignments."
          }
        ],
        "responses": {
          "200": {
            "description": "Published assignments with totalGrade and maxGrade.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course faculty or a system admin (TAs excluded).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdAssignments",
        "summary": "Create a course assignment",
        "description": "Creates an assignment in the course. Course staff (faculty or TAs) or a system  admin. The due date is interpreted as end-of-day in the **course's** timezone. The  late cutoff is optional when late submissions are on (blank means no deadline), must  be omitted when late is off, and must fall on or after the due date when set.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/assignments/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string",
                    "description": "Interpreted as end-of-day in the course's timezone"
                  },
                  "unlockAt": {
                    "type": "string",
                    "description": "Available-from date; must be on or before the due date"
                  },
                  "allowLateSubmissions": {
                    "type": "boolean"
                  },
                  "lateCutoff": {
                    "type": "string",
                    "description": "Required when allowLateSubmissions is true"
                  },
                  "isPublished": {
                    "type": "boolean"
                  },
                  "assignedToEveryone": {
                    "type": "boolean",
                    "description": "When false, only the assignees below are assigned"
                  },
                  "groupSetId": {
                    "type": "string",
                    "nullable": true,
                    "description": "Set for a group assignment (the group set it runs in)"
                  },
                  "assignees": {
                    "type": "array",
                    "description": "Audience when assignedToEveryone is false; each item is one student (userId) or group (groupId)",
                    "items": {
                      "type": "object",
                      "properties": {
                        "userId": {
                          "type": "string"
                        },
                        "groupId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created assignment."
          },
          "400": {
            "description": "Missing fields, or an inconsistent late-submission window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/duplicate": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdDuplicate",
        "summary": "Duplicate a course",
        "description": "Creates a new course modeled on an existing one, in a single transaction. The  copy's faculty comes from the copied faculty roster and/or an explicit  `instructorIds` list; at least one faculty member is required (the caller is  NOT added automatically). TAs are copied only when asked. `copyMode` (or the  legacy copyAssignments/copyProblems booleans) selects what carries over:  assignments only, problems only, or assignments with their problems. The copy  always starts unpublished with a fresh registration code. System administrators  only. Dates are interpreted in the actor's timezone.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/duplicate/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Source course id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "code",
                  "semester",
                  "startDate",
                  "endDate",
                  "registrationOpenAt",
                  "registrationCloseAt",
                  "credits"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string",
                    "description": "Like \"CMPSC 221\" or \"MATH220\""
                  },
                  "semester": {
                    "type": "string"
                  },
                  "credits": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 6
                  },
                  "startDate": {
                    "type": "string"
                  },
                  "endDate": {
                    "type": "string"
                  },
                  "registrationOpenAt": {
                    "type": "string"
                  },
                  "registrationCloseAt": {
                    "type": "string"
                  },
                  "emptyStringNotation": {
                    "type": "string"
                  },
                  "copyMode": {
                    "type": "string",
                    "enum": [
                      "assignments",
                      "problems",
                      "assignments_with_problems"
                    ]
                  },
                  "copyAssignments": {
                    "type": "boolean",
                    "description": "Legacy fallback for copyMode"
                  },
                  "copyProblems": {
                    "type": "boolean",
                    "description": "Legacy fallback for copyMode"
                  },
                  "copyFaculty": {
                    "type": "boolean"
                  },
                  "copyTAs": {
                    "type": "boolean"
                  },
                  "instructorIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Additional faculty for the copy. Combined with copyFaculty, the result must include at least one faculty member."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new course id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields, bad credits, bad code, invalid dates, or no faculty for the copy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "System administrators only (logged as a security event).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/grades/export": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdGradesExport",
        "summary": "Export course grades as an LMS CSV",
        "description": "Builds an import-ready LMS gradebook CSV server-side and returns it as a download.  The export is audited here (atomically with generation) rather than via a separate,  skippable client call. Course staff (faculty or TAs) or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/grades/export/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "canvas",
                "blackboard",
                "moodle",
                "brightspace",
                "generic"
              ]
            }
          },
          {
            "name": "assignments",
            "in": "query",
            "description": "Comma-separated assignment ids, or 'all' / omitted for the whole gradebook",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A CSV file (text/csv) as an attachment."
          },
          "400": {
            "description": "No matching assignments to export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/grades": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdGrades",
        "summary": "Get the course grade matrix",
        "description": "Returns the full gradebook matrix for a course: students x assignments with each cell  holding the student's summed assignment grade (problem grades collapsed into one  total). Course staff (faculty or TAs) or a system admin. Reading the whole gradebook  is a FERPA-relevant access, so it's recorded (throttled) in the audit log.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/grades/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "part",
            "in": "query",
            "required": false,
            "description": "`structure` returns students + assignments; `values` returns just the grades map; omitted returns the full matrix.",
            "schema": {
              "type": "string",
              "enum": [
                "structure",
                "values"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Students, assignments, and a nested grades map (grades[studentId][assignmentId]).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "students": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "assignments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "grades": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}/duplicate": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdGroupSetsBySetIdDuplicate",
        "summary": "Duplicate a group set",
        "description": "Duplicates a group set into a new, independent set in the same course. Copies  groups (and, optionally, current active-student memberships). Inactive students  and non-student roster members are never copied. Nothing else is copied  (no submissions, grades, feedback, assignment links, or activity). Renaming and  duplication stay allowed even when a set is locked. Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/duplicate/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "includeMemberships": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new group set."
          },
          "400": {
            "description": "Missing or invalid name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A group set with that name already exists in the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}/groups/{groupId}": {
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdGroupSetsBySetIdGroupsByGroupId",
        "summary": "Rename a group in a set",
        "description": "Renames a group within its set (unique per set, case-insensitive). Course staff  or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/groups/[groupId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated group."
          },
          "400": {
            "description": "Missing or invalid name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group not found in this set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Name already used by another group in the set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdGroupSetsBySetIdGroupsByGroupId",
        "summary": "Delete a group in a set",
        "description": "Deletes a group (its memberships cascade away). Blocked when the set is locked.  Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/groups/[groupId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Group deleted."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group not found in this set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The set is locked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}/groups": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdGroupSetsBySetIdGroups",
        "summary": "Create a group in a set",
        "description": "Creates a group inside a set. Blocked when the set is locked. Group names are  unique within their set (case-insensitive); different sets may reuse names.  Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/groups/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created group."
          },
          "400": {
            "description": "Missing or invalid name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Duplicate group name in the set or the set is locked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}/memberships": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdGroupSetsBySetIdMemberships",
        "summary": "Bulk change group-set memberships",
        "description": "Atomically assigns, moves, and removes students within a group set. Each  operation sets one student's group (or removes them when groupId is null). A  move is a single upsert on the (set, student) unique key, so a student is never  transiently in two groups. Assign/move targets must be active STUDENTs;  removals are allowed even for inactive members. When expectedBasis is provided  and no longer matches the set's current memberships, the change is rejected with  409 so a stale client cannot silently overwrite another staff member's edit.  Blocked when the set is locked. Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/memberships/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "operations"
                ],
                "properties": {
                  "operations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "userId",
                        "groupId"
                      ],
                      "properties": {
                        "userId": {
                          "type": "string"
                        },
                        "groupId": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "expectedBasis": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated group set detail."
          },
          "400": {
            "description": "Invalid operations or an unknown group or ineligible student.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The set is locked or the memberships changed since expectedBasis.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}/random-assign": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdGroupSetsBySetIdRandomAssign",
        "summary": "Preview a random group assignment",
        "description": "Computes a balanced random-assignment PREVIEW without writing anything. The  client shows the preview, then applies it by POSTing the returned operations to  the memberships endpoint together with `basis` as expectedBasis (so a change by  another staff member since the preview is caught). Never creates groups: a set  with no groups returns 400. Blocked when the set is locked. Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/random-assign/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "studentIds"
                ],
                "properties": {
                  "studentIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "reassignSelected": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview groups plus operations and a basis token."
          },
          "400": {
            "description": "The set has no groups.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The set is locked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets/{setId}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdGroupSetsBySetId",
        "summary": "Get a group set's detail",
        "description": "Full detail for one group set: its groups, each group's members (with an  inactive flag), and the eligible active-student roster. Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The group set detail."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdGroupSetsBySetId",
        "summary": "Rename a group set",
        "description": "Renames a group set (allowed even when locked). Names are unique per course  (case-insensitive). Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated group set."
          },
          "400": {
            "description": "Missing or invalid name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Name already used by another set in the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdGroupSetsBySetId",
        "summary": "Delete a group set",
        "description": "Deletes a group set and its groups + memberships (cascade). Deletion is blocked  when the set has dependencies (none exist yet; the assignment-integration phase  adds submission/grade checks). Dependencies are re-checked inside the delete  transaction. Course staff or admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/[setId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Group set deleted."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Group set not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The group set has dependencies and cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/group-sets": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdGroupSets",
        "summary": "List course group sets",
        "description": "Lists a course's group sets with summary counts. Course staff (faculty or TAs)  or a system admin.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The course's group sets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdGroupSets",
        "summary": "Create a course group set",
        "description": "Creates a group set, optionally seeding a number of empty default-named groups.  Course staff (faculty or TAs) or a system admin. Set names are unique per course  (case-insensitive).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/group-sets/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "initialGroupCount": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created group set."
          },
          "400": {
            "description": "Missing or invalid name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A group set with that name already exists in the course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/lookup-users": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdLookupUsers",
        "summary": "Look up users by email",
        "description": "Resolves a list of emails to user records, splitting them into `found` and  `notFound`, used by the roster importer to preview who exists before enrolling.  Matching is case-insensitive regardless of DB collation. Restricted to course  staff (faculty or TAs) or a system admin: only someone who can manage the course  in the path may resolve emails to accounts.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/lookup-users/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "emails"
                ],
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matched users and the emails with no match.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "found": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "notFound": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/problems/{pid}/duplicate": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdProblemsByPidDuplicate",
        "summary": "Duplicate a course problem",
        "description": "Duplicate a problem within the same course. The title/description come from the  request; the type, state cap, determinism flag, and the solution file (answer key) are  copied from the source and stay editable afterward. The solution file is copied to a  fresh name on disk so the two problems never share a file.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/[pid]/duplicate/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The newly created problem."
          },
          "400": {
            "description": "Invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source problem not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/problems/{pid}": {
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesByIdProblemsByPid",
        "summary": "Update a course problem",
        "description": "Updates a problem within a course (multipart/form-data). Course staff (faculty or  TAs) or a system admin. The problem must belong to the course in the path. Sending a  new file replaces the stored solution; it's structure-validated and size-checked  first, and the previous file is removed. Omitting the file keeps the current one.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/[pid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "type"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "maxStates": {
                    "type": "string"
                  },
                  "isDeterministic": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Optional new solution file"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated problem."
          },
          "400": {
            "description": "Missing fields or the new file failed structure validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "File exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdProblemsByPid",
        "summary": "Delete a course problem",
        "description": "Deletes a problem within a course and its solution file. Course staff (faculty or  TAs) or a system admin. The problem must belong to the course in the path. Refused  while the problem is still attached to any assignment (problems are shared across  assignments many-to-many); otherwise its submissions are removed first, then the  record and file.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/[pid]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Problem deleted."
          },
          "400": {
            "description": "Problem is still linked to an assignment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Problem not found in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/problems/import": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdProblemsImport",
        "summary": "Import a problem from another course",
        "description": "Imports a problem from ANOTHER course the caller can manage into this course. The  title/description come from the request; the type, state cap, determinism flag, and  the solution file (answer key) are copied from the source. The solution file is copied  to a fresh name on disk so the two problems never share a file.   Permission is tiered: the wrapper gates the destination course (manage), and the caller  must also be able to manage the source course.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/import/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Destination course id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sourceCourseId",
                  "sourceProblemId",
                  "title"
                ],
                "properties": {
                  "sourceCourseId": {
                    "type": "string"
                  },
                  "sourceProblemId": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The newly created problem."
          },
          "400": {
            "description": "Invalid body, or the source is the destination course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller cannot manage the destination or the source course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source problem not found in the source course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/problems": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdProblems",
        "summary": "List a course's problems",
        "description": "Lists a course's problem bank (id, title, description, type), for pickers such as the  \"import a problem from another course\" wizard. Course staff (faculty or TAs) or a  system admin; this route is manage-gated, so no student reaches it.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The course's problems.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdProblems",
        "summary": "Create a problem in a course",
        "description": "Creates a problem in a course from an uploaded solution file (multipart/form-data).  Course staff (faculty or TAs) or a system admin. The file's XML structure is  validated against the problem type before it's written to disk, and it's  size-checked against the system upload limit. `maxStates` applies to FA/PDA and  `isDeterministic` to FA. The course comes from the path.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/problems/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "type",
                  "file"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "description": "Problem type (e.g. FA, PDA, RE, CFG)"
                  },
                  "assignmentId": {
                    "type": "string"
                  },
                  "maxStates": {
                    "type": "string",
                    "description": "FA/PDA only"
                  },
                  "isDeterministic": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "description": "FA only"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Solution definition (XML)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created problem."
          },
          "400": {
            "description": "Missing fields or the solution file failed structure validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff (faculty or TA) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "File exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/publish": {
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdPublish",
        "summary": "Publish or unpublish a course",
        "description": "Toggles a course's published state. Course staff (faculty or TA) or a system admin.  Unpublishing runs a  safety check (canUnpublishCourse) that refuses if students would lose access to  work already in progress.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/publish/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "isPublished"
                ],
                "properties": {
                  "isPublished": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated course (id, name, code, isPublished, updatedAt)."
          },
          "400": {
            "description": "isPublished must be a boolean.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff or a system admin, or unpublishing is blocked by the safety check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/roster/{userId}/reset-password": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdRosterByUserIdResetPassword",
        "summary": "Reset a student's password (course staff)",
        "description": "Course-staff password reset for a student on the roster. This is a stop-gap for  small deployments so course staff can help a student who is locked out, without  a system administrator. Permission is tiered: the wrapper admits course FACULTY  and TAs (and global admins); this handler additionally requires the target to be  a STUDENT enrolled in THIS course. It refuses to reset a staff member, a global  administrator, or anyone not on this roster, so it can never be used to seize a  privileged account. System-wide resets stay on the admin User Accounts page.   Not blocked on an archived course: a student may still need to sign in to review  past work, and the reset touches the user account, not course content.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/[userId]/reset-password/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "newPassword"
                ],
                "properties": {
                  "newPassword": {
                    "type": "string",
                    "description": "Must meet the strength policy"
                  },
                  "isTemporary": {
                    "type": "boolean",
                    "description": "Force a change at next login (default false)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password reset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields or weak password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff, or the target is not a student on this roster.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Target user is not on this course roster.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Reset failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/roster/{userId}": {
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesByIdRosterByUserId",
        "summary": "Remove a user from a course",
        "description": "Removes a user from a course roster. Permission is tiered: the shared wrapper  admits global admins and course faculty only (TAs and students are rejected up  front); the remaining rule (a faculty member may not remove another faculty  member) is enforced here (a global admin may). Two safety rules block the removal  outright: the user must have no submissions in the course, and a course can't lose  its last faculty member.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/[userId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed; returns how many roster rows were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "removed": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "User has submissions, or is the only faculty member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller's role may not remove this user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdRosterByUserId",
        "summary": "Get a roster entry",
        "description": "Returns one roster entry (with the user's profile) plus the viewer's own course  role and an `viewerIsAdmin` flag, so the UI can decide which actions to offer.  Access is tiered: the caller must be a member of the course (the wrapper enforces  this), and a non-staff member may only read their OWN entry; course staff  (faculty/TA) and admins may read anyone's. `userId` may be the literal \"me\" to  target the caller.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/[userId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "A user id, or \"me\" for the caller",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The roster entry, the viewer's course role, and viewerIsAdmin."
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a course member, or a non-staff member reading someone else's entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No roster entry for that user in this course.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdRosterByUserId",
        "summary": "Change a user's course role",
        "description": "Changes a user's course role. Only a global admin or a course faculty member may  do this. The last faculty member can't be demoted, keeping every course with  someone in charge.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/[userId]/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "FACULTY",
                      "TA",
                      "STUDENT"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Role updated."
          },
          "400": {
            "description": "Invalid role, or demoting the only faculty member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system admin or a course faculty member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Roster entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/roster/{userId}/status": {
      "patch": {
        "tags": [
          "courses"
        ],
        "operationId": "patchCoursesByIdRosterByUserIdStatus",
        "summary": "Drop or re-enroll a student",
        "description": "Drop or re-enroll a student in a course.   A DROPPED student keeps their roster row and all their work (submissions, grades,  group membership, audience/override rows) but loses access: `canAccessCourse` denies  them, so they can't see or interact with the course, and it disappears from their own  lists. Staff review surfaces still show them, marked \"Dropped\". Re-enrolling flips  them back to ENROLLED and restores everything.   This is distinct from removal (`DELETE .../roster/[userId]`): removal is a hard delete  for a student with no work; drop is the reversible action for a student who has work.  Only a global admin or a course FACULTY member may do it (TAs may not, matching the  other roster mutations), and it applies only to STUDENT rows.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/[userId]/status/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "ENROLLED",
                      "DROPPED"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Status updated (or already at the requested status)."
          },
          "400": {
            "description": "Invalid body, or the target is not a student.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a system admin or a course faculty member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Roster entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Course is archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/roster/bulk": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdRosterBulk",
        "summary": "Bulk-enroll students",
        "description": "Bulk-adds users as STUDENT to a course roster (the roster's bulk-add flow).  Course staff (faculty or TAs) or a system admin. Purely additive: users not yet on  the roster are inserted as STUDENT and anyone already enrolled is left untouched, so  it's idempotent and safe to re-run. Changing an existing member's role is the  dedicated faculty-gated role-change endpoint's job.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/bulk/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userIds"
                ],
                "properties": {
                  "userIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrolled; returns how many.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "enrolled": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No users provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/roster": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdRoster",
        "summary": "Enroll a user in a course",
        "description": "Adds a single user to a course roster. Course staff (faculty or TAs) or a system  admin. A brand-new member is added as STUDENT; callers don't pick the role. Purely  additive: an already-enrolled member keeps their current role (changing a role,  including demoting staff, is the dedicated faculty-gated role-change endpoint's job).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/roster/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId"
                ],
                "properties": {
                  "userId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrolled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing userId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Target user not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The target user is inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesById",
        "summary": "Get a course",
        "description": "Fetches one course with derived metadata, shaped by the `view` query param to  keep payloads lean (full/summary/roster/assignments/problems). Assignments come  back with derived point totals and submission/comment counts; problems are  tagged with whether an assignment uses them; the roster is flattened into a  single `enrolled` array, and the caller's own course role is included. Access is  restricted: any enrolled member of the course (any role) or a system admin.\n\n**Auth:** requires FACULTY / TA / STUDENT\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "view",
            "in": "query",
            "description": "Controls which relations are included.",
            "schema": {
              "type": "string",
              "enum": [
                "full",
                "summary",
                "roster",
                "assignments",
                "problems"
              ],
              "default": "full"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The course with metadata for the requested view."
          },
          "400": {
            "description": "Missing course id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled in the course and not a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "courses"
        ],
        "operationId": "putCoursesById",
        "summary": "Update a course",
        "description": "Updates a course's details and, when `instructorIds` is supplied, reconciles its  faculty roster (adds, promotes, or removes to match the desired set). Runs the  same archive/unpublish safety checks as the dedicated toggles, requires a  registration window, and records a before→after diff of changed fields.  Course staff (faculty or TAs) or a system admin.\n\n**Auth:** requires FACULTY / TA / STUDENT\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "code",
                  "semester",
                  "credits",
                  "startDate",
                  "endDate",
                  "registrationOpenAt",
                  "registrationCloseAt",
                  "isPublished",
                  "isArchived"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "semester": {
                    "type": "string"
                  },
                  "credits": {
                    "type": "number"
                  },
                  "startDate": {
                    "type": "string"
                  },
                  "endDate": {
                    "type": "string"
                  },
                  "registrationOpenAt": {
                    "type": "string"
                  },
                  "registrationCloseAt": {
                    "type": "string"
                  },
                  "isPublished": {
                    "type": "boolean"
                  },
                  "isArchived": {
                    "type": "boolean"
                  },
                  "emptyStringNotation": {
                    "type": "string"
                  },
                  "instructorIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "If present, becomes the exact faculty set"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated course with roster and assignments."
          },
          "400": {
            "description": "Missing id, invalid isArchived, empty instructor list, or missing registration window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin, or an archive/unpublish safety check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "courses"
        ],
        "operationId": "deleteCoursesById",
        "summary": "Delete a course",
        "description": "Deletes a course (system admin only). An **empty** course (no assignments, no  problems, no student enrollments, and no submissions) is removed permanently  (its staff-only roster cascades away; audit logs keep a nulled course pointer).  Any course that holds real work or students is **soft-deleted** instead: the row  and all its data are retained but `deletedAt` is stamped so the access gates and  list queries treat it as gone (recoverable later). The response says which  happened via `{ deleted: 'hard' | 'soft' }`.\n\n**Auth:** requires FACULTY / TA / STUDENT\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Course deleted; body reports whether it was a hard or soft delete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string",
                      "enum": [
                        "hard",
                        "soft"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a system admin (logged as a security event).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/student-grades": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdStudentGrades",
        "summary": "Get my grades for a course",
        "description": "Returns the signed-in student's own grade breakdown for a course: published  assignments, their problems, and per-problem grade, latest submission status,  and attempt count. Available to enrolled members (viewing their own data) and  to staff.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/student-grades/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's per-assignment, per-problem grade breakdown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assignments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing course id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled and not staff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/students": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesByIdStudents",
        "summary": "List a course's students",
        "description": "Returns the STUDENT members of a course (user profiles, each tagged with its  `enrollmentStatus`). Course staff (faculty or TAs) or a system admin.   By default only ACTIVE (ENROLLED) students are returned, since the main caller is the  assignment-audience picker, where a dropped student must not be offered as a new  target. Pass `?includeDropped=1` to also return dropped students (for staff review  surfaces like the submissions view, which show them labeled).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/students/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeDropped",
            "in": "query",
            "required": false,
            "description": "\"1\"/\"true\" also returns dropped students (default: enrolled only).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The course's students, each with an enrollmentStatus.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not course staff (faculty or TAs) or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/{id}/submissions/rerun": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesByIdSubmissionsRerun",
        "summary": "Rerun all submissions in a course",
        "description": "Re-queues every submission in a course, resetting each to PENDING (with a fresh  attempt budget) and clearing its feedback/result: the bulk counterpart to the  single-submission rerun. Course staff (faculty or TAs) or a system admin. Logs one  batch-summary event and returns the count re-queued.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/[id]/submissions/rerun/route.ts)",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Course id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Submissions re-queued; returns the count.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/join": {
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCoursesJoin",
        "summary": "Join a course by registration code",
        "description": "Enrolls the signed-in user in a course via its registration code,  as a STUDENT. Users never learn that an unpublished/archived course exists  (masked as 404). Global admins can't self-enroll, and the registration window  must be open.\n\n**Auth:** requires STUDENT\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/join/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "course registration code"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Joined; returns a message and the course."
          },
          "400": {
            "description": "Invalid code, admin join attempt, already enrolled, or registration not open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Enrollment was dropped; re-enrollment is a staff action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Course not found (also returned for unpublished/archived courses).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCourses",
        "summary": "List all courses",
        "description": "Returns every course with its roster and assignment metadata. System administrators only; the payload spans all courses and includes every member's identity and each course's registration code.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Array of courses, each with an `enrolled` roster and assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "System administrators only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "courses"
        ],
        "operationId": "postCourses",
        "summary": "Create a course",
        "description": "Creates a course (with a generated registration code) and seeds its faculty  roster, all in one transaction. System administrators only. A new course is  always created unpublished (publishing is a separate action) and requires at  least one faculty member. Datetime-local strings are interpreted in the course's  timezone before being stored as UTC.\n\n**Auth:** requires FACULTY / TA\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "code",
                  "semester",
                  "credits",
                  "startDate",
                  "endDate",
                  "registrationOpenAt",
                  "registrationCloseAt",
                  "instructorIds"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "semester": {
                    "type": "string"
                  },
                  "credits": {
                    "type": "number"
                  },
                  "startDate": {
                    "type": "string",
                    "description": "datetime-local string"
                  },
                  "endDate": {
                    "type": "string",
                    "description": "datetime-local string"
                  },
                  "registrationOpenAt": {
                    "type": "string",
                    "description": "datetime-local string"
                  },
                  "registrationCloseAt": {
                    "type": "string",
                    "description": "datetime-local string"
                  },
                  "instructorIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Faculty to seed the roster; at least one is required."
                  },
                  "emptyStringNotation": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Course created (always unpublished); returns the course with its `enrolled` roster."
          },
          "400": {
            "description": "Missing registration window, no faculty, or Zod validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "System administrators only (logged as a security event).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A course with that code and semester already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/courses/userCourses/{email}": {
      "get": {
        "tags": [
          "courses"
        ],
        "operationId": "getCoursesUserCoursesByEmail",
        "summary": "List my started courses",
        "description": "Returns the signed-in user's own started, non-archived courses (id + name),  used by pickers that only make sense for courses already underway. Note: the  `email` path segment is ignored; results are always scoped to the caller's  id, so one user can't enumerate another's courses.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/courses/userCourses/[email]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "Ignored; retained for the route shape. Results use the caller's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's started, non-archived courses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/files/pfps/{file}": {
      "get": {
        "tags": [
          "files"
        ],
        "operationId": "getFilesPfpsByFile",
        "summary": "Get an avatar file",
        "description": "Serves an avatar image from private storage, inline. Any signed-in user may fetch  one (avatars are shown throughout the app). The filename is rejected if it  contains a path-traversal sequence.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/files/pfps/[file]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image bytes (inline).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filename.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/files/problems/{file}": {
      "get": {
        "tags": [
          "files"
        ],
        "operationId": "getFilesProblemsByFile",
        "summary": "Get a problem file",
        "description": "Serves a problem's attached file, inline. **Course staff (faculty/TA) or a system  admin only**: a problem file is the autograder's answer/solution key, so a student  must never receive it (same sensitivity as a solution file). The download is audited,  and traversal filenames are rejected.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/files/problems/[file]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file bytes (inline).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filename.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/files/solutions/{file}": {
      "get": {
        "tags": [
          "files"
        ],
        "operationId": "getFilesSolutionsByFile",
        "summary": "Get a solution file",
        "description": "Serves a problem's solution file, the most sensitive protected material, so  access is limited to course staff (faculty or TAs) or a system admin, and every  successful serve is audited (both inline and `?download=1`). Traversal filenames  are rejected.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/files/solutions/[file]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "download",
            "in": "query",
            "description": "Set to \"1\" to mark the access as a download in the audit log",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The solution file bytes (as an attachment).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filename.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/files/submissions/{file}": {
      "get": {
        "tags": [
          "files"
        ],
        "operationId": "getFilesSubmissionsByFile",
        "summary": "Get a submission file",
        "description": "Serves a submission's uploaded file as a download. Restricted to the submitting  student, course staff (faculty or TAs), or a system admin. The download is audited,  and traversal filenames are rejected.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/files/submissions/[file]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file bytes (as an attachment).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filename.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not the submitting student, course staff, or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "health"
        ],
        "operationId": "getHealth",
        "summary": "status routes (`/api/admin/status/*`).",
        "description": "status routes (`/api/admin/status/*`).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/health/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "uptime": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Health check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me/assignments": {
      "post": {
        "tags": [
          "me"
        ],
        "operationId": "postMeAssignments",
        "summary": "List my assignments in a date range",
        "description": "Returns the assignments visible to the signed-in user whose due dates fall in a  date range: the data behind the calendar view. Role-based visibility is applied  inside getAssignmentsForUserRange. Bare dates are widened to cover the whole day  in the user's timezone.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/assignments/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "start",
                  "end"
                ],
                "properties": {
                  "start": {
                    "type": "string",
                    "description": "Date or datetime; bare dates start at 00:00"
                  },
                  "end": {
                    "type": "string",
                    "description": "Date or datetime; bare dates end at 23:59"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assignments due within the range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing start or end.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me/courses": {
      "get": {
        "tags": [
          "me"
        ],
        "operationId": "getMeCourses",
        "summary": "List my courses",
        "description": "Lists the courses visible to the signed-in user, in one of two shapes selected by  the `view` query param:    - default: the full role-scoped list (a student sees their published      enrollments; staff/admins see more), shaped by getCoursesListForUser.    - `view=nav`: a compact list for the sidebar navigation, only the caller's      enrolled courses (published-only for students), with just the fields the nav      needs (id, name, code, publish/archive flags), newest first.\n\n**Auth:** requires FACULTY / TA / ADMIN / STUDENT\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/courses/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "view",
            "in": "query",
            "description": "\"nav\" returns the compact sidebar shape; omit for the full list.",
            "schema": {
              "type": "string",
              "enum": [
                "nav"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Courses visible to the caller (shape depends on `view`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me/enrollments": {
      "get": {
        "tags": [
          "me"
        ],
        "operationId": "getMeEnrollments",
        "summary": "Inspect my enrollments (debug)",
        "description": "Debug helper: returns the signed-in user's own roster entries (with a little  course info) plus their id and role. Handy for diagnosing enrollment issues;  scoped to the caller, so it exposes nothing about other users.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/enrollments/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's roster entries and role.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "userId": {
                      "type": "string"
                    },
                    "userRole": {
                      "type": "string"
                    },
                    "enrollments": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Query failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me/manageable-courses": {
      "get": {
        "tags": [
          "me"
        ],
        "operationId": "getMeManageableCourses",
        "summary": "List courses the caller can manage",
        "description": "Lists the courses the caller may manage, for the \"import assignment\" picker: courses  where they are FACULTY or TA, or every course when they are a system admin. Archived  courses are included (you can import from a past term); soft-deleted courses never  appear. Pass `excludeCourseId` to drop the course being imported into (importing from  the same course is what Duplicate already does).\n\n**Auth:** requires FACULTY / TA\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/manageable-courses/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "excludeCourseId",
            "in": "query",
            "required": false,
            "description": "A course id to omit from the list (typically the import destination).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Manageable courses (id, name, code, semester, archived flag), active first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me/password": {
      "post": {
        "tags": [
          "me"
        ],
        "operationId": "postMePassword",
        "summary": "Change my password",
        "description": "Lets a signed-in user change their own password. Requires the current password,  enforces the strength policy, and forbids reusing the existing one. A correct  change also clears the `temporaryPassword` flag (used after admin resets).\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/password/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "oldPassword",
                  "newPassword"
                ],
                "properties": {
                  "oldPassword": {
                    "type": "string"
                  },
                  "newPassword": {
                    "type": "string",
                    "description": "Must meet the strength policy and differ from the old one"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing fields, weak password, wrong current password, or reused password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User record not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/me": {
      "post": {
        "tags": [
          "me"
        ],
        "operationId": "postMe",
        "summary": "Update my profile",
        "description": "Updates the signed-in user's own profile: names, timezone, and avatar. The  avatar is written to disk and any previous file is removed; `deleteAvatar`  clears it instead. Sent as multipart/form-data because it carries a file.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "firstName",
                  "lastName"
                ],
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "One of the app's common timezones; blank clears it"
                  },
                  "avatar": {
                    "type": "string",
                    "format": "binary",
                    "description": "New profile image"
                  },
                  "deleteAvatar": {
                    "type": "string",
                    "enum": [
                      "true"
                    ],
                    "description": "Remove the current avatar"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "firstName": {
                      "type": "string"
                    },
                    "lastName": {
                      "type": "string"
                    },
                    "avatar": {
                      "type": "string",
                      "nullable": true
                    },
                    "timezone": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Blank name or invalid timezone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Avatar exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Update failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "me"
        ],
        "operationId": "getMe",
        "summary": "Get my profile",
        "description": "Returns the signed-in user's own profile.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/me/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The current user's profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "firstName": {
                      "type": "string"
                    },
                    "lastName": {
                      "type": "string"
                    },
                    "avatar": {
                      "type": "string",
                      "nullable": true
                    },
                    "timezone": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/session/extend": {
      "post": {
        "tags": [
          "session"
        ],
        "operationId": "postSessionExtend",
        "summary": "Keep the current session active",
        "description": "Records that the signed-in user is still active, resetting the client-side  inactivity timer. This is an audit/telemetry ping; the JWT session lifetime  is managed by NextAuth and is not altered here.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/session/extend/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Activity recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/submissions/{id}/rerun": {
      "post": {
        "tags": [
          "submissions"
        ],
        "operationId": "postSubmissionsByIdRerun",
        "summary": "Rerun a submission",
        "description": "Re-queues one submission for evaluation, resetting it to PENDING and clearing its  prior feedback/result. Course staff (faculty or TAs) or a system admin. The  submission must have a stored file and its problem must still be linked to the  assignment.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/submissions/[id]/rerun/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Submission id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Submission re-queued (status PENDING)."
          },
          "400": {
            "description": "Submission has no file, or its problem is no longer linked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Caller is not course staff or a system admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Submission not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Course is archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/submissions": {
      "post": {
        "tags": [
          "submissions"
        ],
        "operationId": "postSubmissions",
        "summary": "Submit a solution",
        "description": "Submits a student's solution file for one assignment problem (multipart/form-data)  and queues it for evaluation. Requires a signed-in user who is enrolled in the  course (admins may submit anywhere). The problem must be linked to the assignment;  the authoritative course comes from the assignment, not the client. Enforces a  resubmit cooldown (429), the assignment's late/late-cutoff policy (403), an upload  size limit (413), and XML structure validation. On success the submission is  stored PENDING and returned with 202.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/submissions/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "assignmentId",
                  "problemId"
                ],
                "properties": {
                  "assignmentId": {
                    "type": "string"
                  },
                  "problemId": {
                    "type": "string"
                  },
                  "courseId": {
                    "type": "string",
                    "description": "Ignored; derived from the assignment"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The solution file (XML)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Submission accepted and queued (status PENDING)."
          },
          "400": {
            "description": "Missing fields, unlinked problem, or invalid file structure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled, or the late/late-cutoff policy rejected it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Assignment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Per-problem submission limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "File exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Resubmit cooldown in effect (see Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/system-settings/public": {
      "get": {
        "tags": [
          "system-settings"
        ],
        "operationId": "getSystemSettingsPublic",
        "summary": "Get public system settings",
        "description": "The safe subset of system settings the login and signup screens need before a  user is authenticated. Deliberately public and limited to non-sensitive values  (notably the hCaptcha *site* key, never the secret).\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/system-settings/public/route.ts)",
        "security": [],
        "responses": {
          "200": {
            "description": "Public settings for unauthenticated screens.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timezone": {
                      "type": "string"
                    },
                    "allowSignup": {
                      "type": "boolean"
                    },
                    "sessionTimeoutMinutes": {
                      "type": "integer"
                    },
                    "hcaptchaSiteKey": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/users/{id}": {
      "patch": {
        "tags": [
          "users"
        ],
        "operationId": "patchUsersById",
        "summary": "Update a user",
        "description": "Updates a user: names, admin flag, active status, timezone, and avatar. Accepts  either JSON or multipart/form-data (the latter carries the avatar file). A user  may edit themselves; only admins may edit others or change the admin flag.  Deactivation is blocked while the user is still on a published, unarchived  course. Field-level changes are recorded (before → after) in the audit log.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/users/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "isAdmin": {
                    "type": "boolean",
                    "description": "Global admin flag (only writable by admins)"
                  },
                  "inactive": {
                    "type": "boolean"
                  },
                  "email": {
                    "type": "string",
                    "description": "New login email (admins only; must be unused)"
                  },
                  "timezone": {
                    "type": "string"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "inactive": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "avatar": {
                    "type": "string",
                    "format": "binary"
                  },
                  "deleteAvatar": {
                    "type": "string",
                    "enum": [
                      "true"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated user."
          },
          "400": {
            "description": "Invalid timezone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed to edit this user, or deactivating an actively-enrolled user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The change would remove the last active administrator, or the new email is already in use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Avatar exceeds the system upload limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "users"
        ],
        "operationId": "deleteUsersById",
        "summary": "Delete a user",
        "description": "Deletes a user. System administrators only. The user's activity logs are  deliberately preserved (schema `onDelete: SetNull` nulls their userId; each  entry keeps the actor's name/email in metadata), and their avatar file is  cleaned up. The deleted identity is captured for the audit entry before removal.\n\n**Auth:** required\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/users/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "System administrators only (also returned when not signed in), or attempting to delete your own account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "users"
        ],
        "operationId": "getUsersById",
        "summary": "Not supported",
        "description": "This resource only supports PATCH and DELETE.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/users/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "users"
        ],
        "operationId": "postUsersById",
        "summary": "Not supported",
        "description": "This resource only supports PATCH and DELETE.\n\n[View source](https://github.com/PennStateCS/AFCT/blob/main/src/app/api/users/[id]/route.ts)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "next-auth.session-token",
        "description": "NextAuth session cookie (`__Secure-next-auth.session-token` over HTTPS). Role requirements, where they apply, are noted in each operation description."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Error response. Handlers return `error` or `message` with a human-readable reason.",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}