Skip to content

Commit a42b447

Browse files
committed
Implemented service model
1 parent 5e35523 commit a42b447

19 files changed

Lines changed: 3446 additions & 1 deletion

CASEProvider/README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A complete implementation of the **Competencies and Academic Standards Exchange
55
This implementation follows the CASE v1.1 specification from 1EdTech:
66

77
- Specification URL: <https://purl.imsglobal.org/spec/case/v1p1>
8+
- OpenAPI Definition: <https://purl.imsglobal.org/spec/case/v1p1/schema/openapi/imscasev1p1_openapi2_v1p0.json>
89

910
## Overview
1011

@@ -14,6 +15,14 @@ This service provides a REST API that implements the CASE v1.1 specification fro
1415

1516
- ✅ PostgreSQL database with Prisma ORM
1617
- ✅ TypeScript for type safety
18+
- ✅ Six manager endpoints:
19+
- **AssociationsManager**: Manage CFAssociations
20+
- **DefinitionsManager**: Manage CFConcepts, CFSubjects, CFLicenses, CFItemTypes, CFAssociationGroupings
21+
- **DocumentsManager**: Manage CFDocuments
22+
- **ItemsManager**: Manage CFItems
23+
- **PackagesManager**: Manage CFPackages
24+
- **RubricsManager**: Manage CFRubrics and CFRubricCriteria
25+
- ✅ OpenAPI specification endpoint at `/ims/case/v1p1/openapi.json`
1726

1827
## Prerequisites
1928

@@ -95,6 +104,43 @@ The server will start on `http://localhost:3000` (or the PORT specified in your
95104

96105
## API Endpoints
97106

107+
### Base Path
108+
109+
All CASE API endpoints are prefixed with: `/ims/case/v1p1`
110+
111+
### Available Endpoints
112+
113+
#### Associations Manager
114+
115+
- `GET /ims/case/v1p1/CFAssociations/{sourcedId}` - Get a specific association
116+
117+
#### Definitions Manager
118+
119+
- `GET /ims/case/v1p1/CFAssociationGroupings/{sourcedId}` - Get an association grouping
120+
- `GET /ims/case/v1p1/CFConcepts/{sourcedId}` - Get a concept
121+
- `GET /ims/case/v1p1/CFSubjects/{sourcedId}` - Get a subject
122+
- `GET /ims/case/v1p1/CFLicenses/{sourcedId}` - Get a license
123+
- `GET /ims/case/v1p1/CFItemTypes/{sourcedId}` - Get an item type
124+
125+
#### Documents Manager
126+
127+
- `GET /ims/case/v1p1/CFDocuments` - List all documents
128+
- `GET /ims/case/v1p1/CFDocuments/{sourcedId}` - Get a specific document
129+
130+
#### Items Manager
131+
132+
- `GET /ims/case/v1p1/CFItems` - List all items
133+
- `GET /ims/case/v1p1/CFItems/{sourcedId}` - Get a specific item
134+
135+
#### Packages Manager
136+
137+
- `GET /ims/case/v1p1/CFPackages/{sourcedId}` - Get a package
138+
139+
#### Rubrics Manager
140+
141+
- `GET /ims/case/v1p1/CFRubrics/{sourcedId}` - Get a rubric
142+
- `GET /ims/case/v1p1/CFRubricCriteria/{sourcedId}` - Get rubric criteria
143+
98144
#### Utilities
99145

100146
- `GET /` - Service information
@@ -128,12 +174,23 @@ CASEProvider/
128174
├── prisma/
129175
│ ├── migrations/ # Database migrations
130176
│ └── schema.prisma # Database schema
177+
├── public/
178+
│ └── openapi-spec.json # CASE v1.1 OpenAPI specification
131179
├── src/
132180
│ ├── index.ts # Application entry point
133181
│ ├── lib/
182+
│ │ ├── errors.ts # Error handling utilities
134183
│ │ ├── prisma.ts # Prisma client
184+
│ │ └── validation.ts # Validation utilities
135185
│ ├── routes/
136-
│ │ ├── upload.ts # Upload routes
186+
│ ├── associations.ts # Associations endpoints
187+
│ ├── definitions.ts # Definitions endpoints
188+
│ ├── documents.ts # Documents endpoints
189+
│ ├── items.ts # Items endpoints
190+
│ ├── packages.ts # Packages endpoints
191+
│ ├── rubrics.ts # Rubrics endpoints
192+
│ ├── spec.ts # OpenAPI spec endpoint
193+
│ └── upload.ts # Upload endpoints
137194
├── package.json
138195
├── tsconfig.json
139196
└── README.md

CASEProvider/public/openapi-spec.json

Lines changed: 2240 additions & 0 deletions
Large diffs are not rendered by default.

CASEProvider/src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import cors from 'cors';
33
import dotenv from 'dotenv';
44
import { uploadRouter } from './routes/upload';
55
import { nullValueCleanup } from './middlewares/json';
6+
import { BASE_PATH } from './routes/constants';
7+
import { associationsRouter } from './routes/associations';
8+
import { definitionsRouter } from './routes/definitions';
9+
import { documentsRouter } from './routes/documents';
10+
import { itemsRouter } from './routes/items';
11+
import { packagesRouter } from './routes/packages';
12+
import { rubricsRouter } from './routes/rubrics';
613

714
dotenv.config();
815

@@ -15,6 +22,12 @@ app.use(nullValueCleanup);
1522

1623
// Mount routers
1724
app.use("/", uploadRouter);
25+
app.use(BASE_PATH, associationsRouter);
26+
app.use(BASE_PATH, definitionsRouter);
27+
app.use(BASE_PATH, documentsRouter);
28+
app.use(BASE_PATH, itemsRouter);
29+
app.use(BASE_PATH, packagesRouter);
30+
app.use(BASE_PATH, rubricsRouter);
1831

1932
// Health check endpoint
2033
app.get('/health', (req, res) => {

CASEProvider/src/lib/errors.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Response } from "express";
2+
3+
type CodeMinorField = {
4+
imsx_codeMinorFieldName: string;
5+
imsx_codeMinorFieldValue: string;
6+
};
7+
8+
type CodeMinor = {
9+
imsx_codeMinorField: CodeMinorField[];
10+
};
11+
12+
type StatusInfo = {
13+
imsx_codeMajor: string;
14+
imsx_severity: string;
15+
imsx_description: string;
16+
imsx_codeMinor?: CodeMinor;
17+
};
18+
19+
export const sendError = (
20+
res: Response,
21+
statusCode: number,
22+
description: string,
23+
field?: string,
24+
codeMinor?: string
25+
) => {
26+
const statusInfo: StatusInfo = {
27+
imsx_codeMajor: "failure",
28+
imsx_severity: "error",
29+
imsx_description: description,
30+
};
31+
32+
if (codeMinor) {
33+
statusInfo.imsx_codeMinor = {
34+
imsx_codeMinorField: [
35+
{
36+
imsx_codeMinorFieldName: field || "general",
37+
imsx_codeMinorFieldValue: codeMinor,
38+
},
39+
],
40+
};
41+
}
42+
43+
res.status(statusCode).json(statusInfo);
44+
};
45+
46+
export const errors = {
47+
notFound: (res: Response, message = "Unknown Object") =>
48+
sendError(res, 404, message, "unknownobject"),
49+
50+
unauthorized: (res: Response) =>
51+
sendError(
52+
res,
53+
401,
54+
"The request was not correctly authorised",
55+
"unauthorisedrequest"
56+
),
57+
58+
forbidden: (res: Response) =>
59+
sendError(
60+
res,
61+
403,
62+
"The server refuses to take any further action",
63+
"forbidden"
64+
),
65+
66+
invalidSelection: (res: Response, field: string) =>
67+
sendError(
68+
res,
69+
400,
70+
"An invalid selection field was supplied",
71+
field,
72+
"invalid_selection_field"
73+
),
74+
75+
invalidQueryParameter: (res: Response, field: string) =>
76+
sendError(
77+
res,
78+
400,
79+
"An invalid query parameter was supplied",
80+
field,
81+
"invalid_query_parameter"
82+
),
83+
84+
serverBusy: (res: Response) =>
85+
sendError(
86+
res,
87+
429,
88+
"The server is receiving too many requests",
89+
"server_busy"
90+
),
91+
92+
internalError: (res: Response) =>
93+
sendError(res, 500, "Internal server error", "internal_server_error"),
94+
};

0 commit comments

Comments
 (0)