diff --git a/backend/Dockerfile b/backend/Dockerfile index 7c63c4b8..4226aa3a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,11 +1,14 @@ -FROM node:22 AS build-stage +FROM node:22 -WORKDIR /app +WORKDIR /app/dwengo + +COPY ./backend/i18n ./i18n # Install dependencies COPY package*.json ./ COPY backend/package.json ./backend/ +COPY common/package.json ./common/ RUN npm install --silent @@ -14,25 +17,13 @@ RUN npm install --silent # Root tsconfig.json COPY tsconfig.json ./ -WORKDIR /app/backend +COPY backend ./backend +COPY common ./common +COPY docs ./docs -COPY backend ./ -COPY docs /app/docs - -RUN npm run build - -FROM node:22 AS production-stage - -WORKDIR /app - -COPY package-lock.json backend/package.json ./ - -RUN npm install --silent --only=production - -COPY ./docs /docs -COPY ./backend/i18n /app/i18n -COPY --from=build-stage /app/backend/dist ./dist/ +RUN npm run build --workspace=common +RUN npm run build --workspace=backend EXPOSE 3000 -CMD ["node", "--env-file=.env", "dist/app.js"] +CMD ["node", "--env-file=/app/dwengo/backend/.env", "/app/dwengo/backend/dist/app.js"] diff --git a/backend/package.json b/backend/package.json index c08fb1dc..c09ac285 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,6 +24,7 @@ "cross": "^1.0.0", "cross-env": "^7.0.3", "dotenv": "^16.4.7", + "dwengo-1-common": "^0.1.1", "express": "^5.0.1", "express-jwt": "^8.5.1", "gift-pegjs": "^1.0.2", diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index c8dd1ec8..21bd568c 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; -import { AssignmentDTO } from '../interfaces/assignment.js'; + +import { AssignmentDTO } from 'dwengo-1-common/src/interfaces/assignment'; // Typescript is annoying with parameter forwarding from class.ts interface AssignmentParams { diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 7526f7c4..40683c6a 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/classes.js'; -import { ClassDTO } from '../interfaces/class.js'; + +import { ClassDTO } from 'dwengo-1-common/src/interfaces/class'; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 7de3e114..6258cb20 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; -import { GroupDTO } from '../interfaces/group.js'; + +import { GroupDTO } from 'dwengo-1-common/src/interfaces/group'; // Typescript is annoywith with parameter forwarding from class.ts interface GroupParams { diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index fc79ef0d..710999ce 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -1,12 +1,12 @@ import { Request, Response } from 'express'; import { FALLBACK_LANG } from '../config.js'; -import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../interfaces/learning-content.js'; import learningObjectService from '../services/learning-objects/learning-object-service.js'; import { envVars, getEnvVar } from '../util/envVars.js'; import { Language } from '../entities/content/language.js'; import attachmentService from '../services/learning-objects/attachment-service.js'; import { NotFoundError } from '@mikro-orm/core'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; +import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { if (!req.params.hruid) { diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 6735c305..089931f3 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -1,9 +1,9 @@ import { Request, Response } from 'express'; import { createQuestion, deleteQuestion, getAllQuestions, getAnswersByQuestion, getQuestion } from '../services/questions.js'; -import { QuestionDTO, QuestionId } from '../interfaces/question.js'; import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { Language } from '../entities/content/language.js'; +import { QuestionDTO, QuestionId } from 'dwengo-1-common/src/interfaces/question'; function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { const { hruid, version } = req.params; diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 67c1d3a9..df0f6e48 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,7 +1,8 @@ import { Request, Response } from 'express'; import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; import { Language, languageMap } from '../entities/content/language.js'; -import { SubmissionDTO } from '../interfaces/submission'; + +import { SubmissionDTO } from 'dwengo-1-common/src/interfaces/submission'; interface SubmissionParams { hruid: string; diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index e352a10a..db009e7a 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -1,11 +1,11 @@ import { Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; -import { Language } from './language.js'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; import { v4 } from 'uuid'; import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; import { EducationalGoal } from './educational-goal.entity.js'; +import { Language } from './language.js'; import { ReturnValue } from './return-value.entity.js'; @Entity({ repository: () => LearningObjectRepository }) diff --git a/backend/src/interfaces/answer.ts b/backend/src/interfaces/answer.ts index 1162a1d3..f268730a 100644 --- a/backend/src/interfaces/answer.ts +++ b/backend/src/interfaces/answer.ts @@ -1,14 +1,7 @@ -import { mapToUserDTO, UserDTO } from './user.js'; -import {mapToQuestionDTO, mapToQuestionDTOId, QuestionDTO, QuestionId} from './question.js'; +import { mapToUserDTO } from './user.js'; +import { mapToQuestionDTO, mapToQuestionDTOId } from './question.js'; import { Answer } from '../entities/questions/answer.entity.js'; - -export interface AnswerDTO { - author: UserDTO; - toQuestion: QuestionDTO; - sequenceNumber: number; - timestamp: string; - content: string; -} +import { AnswerDTO, AnswerId } from 'dwengo-1-common/src/interfaces/answer'; /** * Convert a Question entity to a DTO format. @@ -23,16 +16,10 @@ export function mapToAnswerDTO(answer: Answer): AnswerDTO { }; } -export interface AnswerId { - author: string; - toQuestion: QuestionId; - sequenceNumber: number; -} - -export function mapToAnswerDTOId(answer: Answer): AnswerId { +export function mapToAnswerId(answer: AnswerDTO): AnswerId { return { author: answer.author.username, toQuestion: mapToQuestionDTOId(answer.toQuestion), - sequenceNumber: answer.sequenceNumber!, + sequenceNumber: answer.sequenceNumber, }; } diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 698b5b40..84cbd0cf 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -1,19 +1,9 @@ import { FALLBACK_LANG } from '../config.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; -import { languageMap } from '../entities/content/language.js'; -import { GroupDTO } from './group.js'; import { getLogger } from '../logging/initalize.js'; - -export interface AssignmentDTO { - id: number; - class: string; // Id of class 'within' - title: string; - description: string; - learningPath: string; - language: string; - groups?: GroupDTO[] | string[]; // TODO -} +import { languageMap } from '../entities/content/language.js'; +import { AssignmentDTO } from 'dwengo-1-common/src/interfaces/assignment'; export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index ea1d4901..c711b94b 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -2,14 +2,7 @@ import { Collection } from '@mikro-orm/core'; import { Class } from '../entities/classes/class.entity.js'; import { Student } from '../entities/users/student.entity.js'; import { Teacher } from '../entities/users/teacher.entity.js'; - -export interface ClassDTO { - id: string; - displayName: string; - teachers: string[]; - students: string[]; - joinRequests: string[]; -} +import { ClassDTO } from 'dwengo-1-common/src/interfaces/class'; export function mapToClassDTO(cls: Class): ClassDTO { return { diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index a25c5b8e..9f71e3f7 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -1,12 +1,7 @@ import { Group } from '../entities/assignments/group.entity.js'; -import { AssignmentDTO, mapToAssignmentDTO } from './assignment.js'; -import { mapToStudentDTO, StudentDTO } from './student.js'; - -export interface GroupDTO { - assignment: number | AssignmentDTO; - groupNumber: number; - members: string[] | StudentDTO[]; -} +import { mapToAssignmentDTO } from './assignment.js'; +import { mapToStudentDTO } from './student.js'; +import { GroupDTO } from 'dwengo-1-common/src/interfaces/group'; export function mapToGroupDTO(group: Group): GroupDTO { return { diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index b46e324f..e4b1eb8f 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,14 +1,6 @@ import { Question } from '../entities/questions/question.entity.js'; -import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; -import { mapToStudentDTO, StudentDTO } from './student.js'; - -export interface QuestionDTO { - learningObjectIdentifier: LearningObjectIdentifier; - sequenceNumber?: number; - author: StudentDTO; - timestamp?: string; - content: string; -} +import { mapToStudentDTO } from './student.js'; +import { QuestionDTO, QuestionId } from 'dwengo-1-common/src/interfaces/question'; function getLearningObjectIdentifier(question: Question): LearningObjectIdentifier { return { @@ -33,11 +25,6 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { }; } -export interface QuestionId { - learningObjectIdentifier: LearningObjectIdentifier; - sequenceNumber: number; -} - export function mapToQuestionDTOId(question: Question): QuestionId { const learningObjectIdentifier = getLearningObjectIdentifier(question); diff --git a/backend/src/interfaces/student.ts b/backend/src/interfaces/student.ts index 5297939a..e1e4253f 100644 --- a/backend/src/interfaces/student.ts +++ b/backend/src/interfaces/student.ts @@ -1,12 +1,6 @@ import { Student } from '../entities/users/student.entity.js'; import { getStudentRepository } from '../data/repositories.js'; - -export interface StudentDTO { - id: string; - username: string; - firstName: string; - lastName: string; -} +import { StudentDTO } from 'dwengo-1-common/src/interfaces/student'; export function mapToStudentDTO(student: Student): StudentDTO { return { diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index 98cc4f22..9a506b27 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,26 +1,7 @@ import { Submission } from '../entities/assignments/submission.entity.js'; -import { Language } from '../entities/content/language.js'; -import { GroupDTO, mapToGroupDTO } from './group.js'; -import { mapToStudent, mapToStudentDTO, StudentDTO } from './student.js'; -import { LearningObjectIdentifier } from './learning-content.js'; - -export interface SubmissionDTO { - learningObjectIdentifier: LearningObjectIdentifier; - - submissionNumber?: number; - submitter: StudentDTO; - time?: Date; - group?: GroupDTO; - content: string; -} - -export interface SubmissionDTOId { - learningObjectHruid: string; - learningObjectLanguage: Language; - learningObjectVersion: number; - - submissionNumber?: number; -} +import { mapToGroupDTO } from './group.js'; +import { mapToStudent, mapToStudentDTO } from './student.js'; +import { SubmissionDTO, SubmissionDTOId } from 'dwengo-1-common/src/interfaces/submission'; export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { return { diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index cddef566..8df92fd1 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -1,12 +1,7 @@ import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; -import { ClassDTO, mapToClassDTO } from './class.js'; -import { mapToUserDTO, UserDTO } from './user.js'; - -export interface TeacherInvitationDTO { - sender: string | UserDTO; - receiver: string | UserDTO; - class: string | ClassDTO; -} +import { mapToClassDTO } from './class.js'; +import { mapToUserDTO } from './user.js'; +import { TeacherInvitationDTO } from 'dwengo-1-common/src/interfaces/teacher-invitation'; export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { return { diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts index 31b4723f..a1ba33fd 100644 --- a/backend/src/interfaces/teacher.ts +++ b/backend/src/interfaces/teacher.ts @@ -1,18 +1,6 @@ import { Teacher } from '../entities/users/teacher.entity.js'; import { getTeacherRepository } from '../data/repositories.js'; - -export interface TeacherDTO { - id: string; - username: string; - firstName: string; - lastName: string; - endpoints?: { - classes: string; - questions: string; - invitations: string; - groups: string; - }; -} +import { TeacherDTO } from 'dwengo-1-common/src/interfaces/teacher'; export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { return { diff --git a/backend/src/interfaces/user.ts b/backend/src/interfaces/user.ts index 58f0dd5a..ecc9dd77 100644 --- a/backend/src/interfaces/user.ts +++ b/backend/src/interfaces/user.ts @@ -1,17 +1,5 @@ import { User } from '../entities/users/user.entity.js'; - -export interface UserDTO { - id?: string; - username: string; - firstName: string; - lastName: string; - endpoints?: { - self: string; - classes: string; - questions: string; - invitations: string; - }; -} +import { UserDTO } from 'dwengo-1-common/src/interfaces/user'; export function mapToUserDTO(user: User): UserDTO { return { diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 22c5ce9e..62ea31e6 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,6 +1,8 @@ import { getAssignmentRepository, getClassRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; -import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; -import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; +import { mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; +import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; +import { AssignmentDTO } from 'dwengo-1-common/src/interfaces/assignment'; +import { SubmissionDTO, SubmissionDTOId } from 'dwengo-1-common/src/interfaces/submission'; import { getLogger } from '../logging/initalize.js'; export async function getAllAssignments(classid: string, full: boolean): Promise { diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 7e773fbf..42f4640f 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -1,10 +1,13 @@ import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; -import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; -import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; -import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; +import { mapToClassDTO } from '../interfaces/class.js'; +import { mapToStudentDTO } from '../interfaces/student.js'; +import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds } from '../interfaces/teacher-invitation.js'; import { getLogger } from '../logging/initalize.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; import { Class } from '../entities/classes/class.entity.js'; +import { ClassDTO } from 'dwengo-1-common/src/interfaces/class'; +import { TeacherInvitationDTO } from 'dwengo-1-common/src/interfaces/teacher-invitation'; +import { StudentDTO } from 'dwengo-1-common/src/interfaces/student'; const logger = getLogger(); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 16895e0a..5d35de55 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -6,8 +6,10 @@ import { getSubmissionRepository, } from '../data/repositories.js'; import { Group } from '../entities/assignments/group.entity.js'; -import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; -import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; +import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; +import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; +import { GroupDTO } from 'dwengo-1-common/src/interfaces/group'; +import { SubmissionDTO, SubmissionDTOId } from 'dwengo-1-common/src/interfaces/submission'; import { getLogger } from '../logging/initalize.js'; export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 43af1aca..6dfd2704 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -1,6 +1,12 @@ import { DWENGO_API_BASE } from '../config.js'; import { fetchWithLogging } from '../util/api-helper.js'; -import { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse } from '../interfaces/learning-content.js'; + +import { + FilteredLearningObject, + LearningObjectMetadata, + LearningObjectNode, + LearningPathResponse, +} from 'dwengo-1-common/src/interfaces/learning-content'; import { getLogger } from '../logging/initalize.js'; function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { diff --git a/backend/src/services/learning-objects/attachment-service.ts b/backend/src/services/learning-objects/attachment-service.ts index 4ff4ec47..2acf90a0 100644 --- a/backend/src/services/learning-objects/attachment-service.ts +++ b/backend/src/services/learning-objects/attachment-service.ts @@ -1,6 +1,7 @@ import { getAttachmentRepository } from '../../data/repositories.js'; import { Attachment } from '../../entities/content/attachment.entity.js'; -import { LearningObjectIdentifier } from '../../interfaces/learning-content.js'; + +import { LearningObjectIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; const attachmentService = { async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise { diff --git a/backend/src/services/learning-objects/database-learning-object-provider.ts b/backend/src/services/learning-objects/database-learning-object-provider.ts index a8055f2c..0639782c 100644 --- a/backend/src/services/learning-objects/database-learning-object-provider.ts +++ b/backend/src/services/learning-objects/database-learning-object-provider.ts @@ -1,5 +1,4 @@ import { LearningObjectProvider } from './learning-object-provider.js'; -import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; import { getUrlStringForLearningObject } from '../../util/links.js'; @@ -7,6 +6,7 @@ import processingService from './processing/processing-service.js'; import { NotFoundError } from '@mikro-orm/core'; import learningObjectService from './learning-object-service.js'; import { getLogger, Logger } from '../../logging/initalize.js'; +import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; const logger: Logger = getLogger(); diff --git a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts index dfee329d..35d9f899 100644 --- a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts +++ b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts @@ -1,5 +1,8 @@ import { DWENGO_API_BASE } from '../../config.js'; import { fetchWithLogging } from '../../util/api-helper.js'; +import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js'; +import { LearningObjectProvider } from './learning-object-provider.js'; +import { getLogger, Logger } from '../../logging/initalize.js'; import { FilteredLearningObject, LearningObjectIdentifier, @@ -7,10 +10,7 @@ import { LearningObjectNode, LearningPathIdentifier, LearningPathResponse, -} from '../../interfaces/learning-content.js'; -import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js'; -import { LearningObjectProvider } from './learning-object-provider.js'; -import { getLogger, Logger } from '../../logging/initalize.js'; +} from 'dwengo-1-common/src/interfaces/learning-content'; const logger: Logger = getLogger(); @@ -90,7 +90,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { metadataUrl, `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, { - params: id, + params: { ...id }, } ); @@ -123,7 +123,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { async getLearningObjectHTML(id: LearningObjectIdentifier): Promise { const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw`; const html = await fetchWithLogging(htmlUrl, `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, { - params: id, + params: { ...id }, }); if (!html) { diff --git a/backend/src/services/learning-objects/learning-object-provider.ts b/backend/src/services/learning-objects/learning-object-provider.ts index 81b4d228..6ea7cbd1 100644 --- a/backend/src/services/learning-objects/learning-object-provider.ts +++ b/backend/src/services/learning-objects/learning-object-provider.ts @@ -1,4 +1,4 @@ -import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; +import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; export interface LearningObjectProvider { /** diff --git a/backend/src/services/learning-objects/learning-object-service.ts b/backend/src/services/learning-objects/learning-object-service.ts index 59ffb643..7a4dd828 100644 --- a/backend/src/services/learning-objects/learning-object-service.ts +++ b/backend/src/services/learning-objects/learning-object-service.ts @@ -1,8 +1,8 @@ -import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider.js'; import { LearningObjectProvider } from './learning-object-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; import databaseLearningObjectProvider from './database-learning-object-provider.js'; +import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; function getProvider(id: LearningObjectIdentifier): LearningObjectProvider { if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { diff --git a/backend/src/services/learning-objects/processing/markdown/dwengo-marked-renderer.ts b/backend/src/services/learning-objects/processing/markdown/dwengo-marked-renderer.ts index d1c797be..3b9ad4e5 100644 --- a/backend/src/services/learning-objects/processing/markdown/dwengo-marked-renderer.ts +++ b/backend/src/services/learning-objects/processing/markdown/dwengo-marked-renderer.ts @@ -8,13 +8,13 @@ import InlineImageProcessor from '../image/inline-image-processor.js'; import * as marked from 'marked'; import { getUrlStringForLearningObjectHTML, isValidHttpUrl } from '../../../../util/links.js'; import { ProcessingError } from '../processing-error.js'; -import { LearningObjectIdentifier } from '../../../../interfaces/learning-content.js'; import { Language } from '../../../../entities/content/language.js'; import Image = marked.Tokens.Image; import Heading = marked.Tokens.Heading; import Link = marked.Tokens.Link; import RendererObject = marked.RendererObject; +import { LearningObjectIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; const prefixes = { learningObject: '@learning-object', diff --git a/backend/src/services/learning-objects/processing/processing-service.ts b/backend/src/services/learning-objects/processing/processing-service.ts index f731eb5d..d0d43061 100644 --- a/backend/src/services/learning-objects/processing/processing-service.ts +++ b/backend/src/services/learning-objects/processing/processing-service.ts @@ -13,9 +13,9 @@ import GiftProcessor from './gift/gift-processor.js'; import { LearningObject } from '../../../entities/content/learning-object.entity.js'; import Processor from './processor.js'; import { DwengoContentType } from './content-type.js'; -import { LearningObjectIdentifier } from '../../../interfaces/learning-content.js'; import { Language } from '../../../entities/content/language.js'; import { replaceAsync } from '../../../util/async.js'; +import { LearningObjectIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = //g; const LEARNING_OBJECT_DOES_NOT_EXIST = "
"; diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 3d883ef2..d0fcf630 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -1,5 +1,4 @@ import { LearningPathProvider } from './learning-path-provider.js'; -import { FilteredLearningObject, LearningObjectNode, LearningPath, LearningPathResponse, Transition } from '../../interfaces/learning-content.js'; import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js'; import { getLearningPathRepository } from '../../data/repositories.js'; import { Language } from '../../entities/content/language.js'; @@ -7,6 +6,13 @@ import learningObjectService from '../learning-objects/learning-object-service.j import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; import { getLastSubmissionForCustomizationTarget, isTransitionPossible, PersonalizationTarget } from './learning-path-personalization-util.js'; +import { + FilteredLearningObject, + LearningObjectNode, + LearningPath, + LearningPathResponse, + Transition, +} from 'dwengo-1-common/src/interfaces/learning-content'; /** * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its diff --git a/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts b/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts index a6093bb4..b7f03d35 100644 --- a/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts +++ b/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts @@ -1,8 +1,8 @@ import { fetchWithLogging } from '../../util/api-helper.js'; import { DWENGO_API_BASE } from '../../config.js'; -import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; import { LearningPathProvider } from './learning-path-provider.js'; import { getLogger, Logger } from '../../logging/initalize.js'; +import { LearningPath, LearningPathResponse } from 'dwengo-1-common/src/interfaces/learning-content'; const logger: Logger = getLogger(); diff --git a/backend/src/services/learning-paths/learning-path-provider.ts b/backend/src/services/learning-paths/learning-path-provider.ts index 5e2a09df..a904296a 100644 --- a/backend/src/services/learning-paths/learning-path-provider.ts +++ b/backend/src/services/learning-paths/learning-path-provider.ts @@ -1,6 +1,6 @@ -import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; import { Language } from '../../entities/content/language.js'; import { PersonalizationTarget } from './learning-path-personalization-util.js'; +import { LearningPath, LearningPathResponse } from 'dwengo-1-common/src/interfaces/learning-content'; /** * Generic interface for a service which provides access to learning paths from a data source. diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index f8e328b2..bebfe8a9 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -1,9 +1,9 @@ -import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; import { Language } from '../../entities/content/language.js'; import { PersonalizationTarget } from './learning-path-personalization-util.js'; +import { LearningPath, LearningPathResponse } from 'dwengo-1-common/src/interfaces/learning-content'; const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index a456d9d1..314b9d65 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,7 +1,8 @@ import { getSubmissionRepository } from '../data/repositories.js'; import { Language } from '../entities/content/language.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; -import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; +import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; +import { SubmissionDTO } from 'dwengo-1-common/src/interfaces/submission'; export async function getSubmission( learningObjectHruid: string, diff --git a/backend/src/util/api-helper.ts b/backend/src/util/api-helper.ts index 9aac5bd1..af36532d 100644 --- a/backend/src/util/api-helper.ts +++ b/backend/src/util/api-helper.ts @@ -1,6 +1,6 @@ import axios, { AxiosRequestConfig } from 'axios'; import { getLogger, Logger } from '../logging/initalize.js'; -import { LearningObjectIdentifier } from '../interfaces/learning-content.js'; +import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; const logger: Logger = getLogger(); diff --git a/backend/src/util/links.ts b/backend/src/util/links.ts index ff334eb5..bd14bfba 100644 --- a/backend/src/util/links.ts +++ b/backend/src/util/links.ts @@ -1,4 +1,4 @@ -import { LearningObjectIdentifier } from '../interfaces/learning-content'; +import { LearningObjectIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; export function isValidHttpUrl(url: string): boolean { try { diff --git a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts index 2c3b7dfc..e87c158b 100644 --- a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts +++ b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts @@ -5,11 +5,11 @@ import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-w import { LearningObject } from '../../../src/entities/content/learning-object.entity'; import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; -import { FilteredLearningObject } from '../../../src/interfaces/learning-content'; import { Language } from '../../../src/entities/content/language'; import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; import { LearningPath } from '../../../src/entities/content/learning-path.entity'; +import { FilteredLearningObject } from 'dwengo-1-common/src/interfaces/learning-content'; async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { const learningObjectRepo = getLearningObjectRepository(); diff --git a/backend/tests/services/learning-objects/learning-object-service.test.ts b/backend/tests/services/learning-objects/learning-object-service.test.ts index f284b939..2e96c4fc 100644 --- a/backend/tests/services/learning-objects/learning-object-service.test.ts +++ b/backend/tests/services/learning-objects/learning-object-service.test.ts @@ -4,11 +4,11 @@ import { LearningObject } from '../../../src/entities/content/learning-object.en import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; -import { LearningObjectIdentifier, LearningPathIdentifier } from '../../../src/interfaces/learning-content'; import { Language } from '../../../src/entities/content/language'; import { envVars, getEnvVar } from '../../../src/util/envVars'; import { LearningPath } from '../../../src/entities/content/learning-path.entity'; import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; +import { LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content'; const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifier = { @@ -105,7 +105,10 @@ describe('LearningObjectService', () => { expect(new Set(result.map((it) => it.key))).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); }); it('returns an empty list when queried with a non-existing learning path id', async () => { - const result = await learningObjectService.getLearningObjectsFromPath({ hruid: 'non_existing', language: Language.Dutch }); + const result = await learningObjectService.getLearningObjectsFromPath({ + hruid: 'non_existing', + language: Language.Dutch, + }); expect(result).toEqual([]); }); }); @@ -120,7 +123,10 @@ describe('LearningObjectService', () => { expect(new Set(result)).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); }); it('returns an empty list when queried with a non-existing learning path id', async () => { - const result = await learningObjectService.getLearningObjectIdsFromPath({ hruid: 'non_existing', language: Language.Dutch }); + const result = await learningObjectService.getLearningObjectIdsFromPath({ + hruid: 'non_existing', + language: Language.Dutch, + }); expect(result).toEqual([]); }); }); diff --git a/backend/tests/services/learning-path/database-learning-path-provider.test.ts b/backend/tests/services/learning-path/database-learning-path-provider.test.ts index a5943f5f..7b1f2bf8 100644 --- a/backend/tests/services/learning-path/database-learning-path-provider.test.ts +++ b/backend/tests/services/learning-path/database-learning-path-provider.test.ts @@ -19,7 +19,8 @@ import { createConditionTestLearningPathAndLearningObjects, } from '../../test-assets/learning-paths/test-conditions-example.js'; import { Student } from '../../../src/entities/users/student.entity.js'; -import { LearningObjectNode, LearningPathResponse } from '../../../src/interfaces/learning-content.js'; + +import { LearningObjectNode, LearningPathResponse } from 'dwengo-1-common/src/interfaces/learning-content'; async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { const learningObjectRepo = getLearningObjectRepository(); diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index c3fa5a43..374bf6a7 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -1,8 +1,8 @@ import { AssertionError } from 'node:assert'; import { LearningObject } from '../../src/entities/content/learning-object.entity'; -import { FilteredLearningObject, LearningPath } from '../../src/interfaces/learning-content'; import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; import { expect } from 'vitest'; +import { FilteredLearningObject, LearningPath } from 'dwengo-1-common/src/interfaces/learning-content'; // Ignored properties because they belang for example to the class, not to the entity itself. const IGNORE_PROPERTIES = ['parent']; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 2dd3998d..a47827ba 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -4,6 +4,8 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./dist", - "resolveJsonModule": true + "resolveJsonModule": true, + "sourceMap": true, + "composite": true } } diff --git a/common/package.json b/common/package.json new file mode 100644 index 00000000..db8adfd0 --- /dev/null +++ b/common/package.json @@ -0,0 +1,16 @@ +{ + "name": "dwengo-1-common", + "version": "0.1.1", + "description": "Common types and utilities for Dwengo-1", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --project tsconfig.json", + "format": "prettier --write src/", + "format-check": "prettier --check src/", + "lint": "eslint . --fix" + }, + "exports": { + "./src/*": "./dist/*" + } +} diff --git a/common/src/interfaces/answer.d.ts b/common/src/interfaces/answer.d.ts new file mode 100644 index 00000000..d2faf87d --- /dev/null +++ b/common/src/interfaces/answer.d.ts @@ -0,0 +1,16 @@ +import { UserDTO } from './user'; +import { QuestionDTO } from './question'; + +export interface AnswerDTO { + author: UserDTO; + toQuestion: QuestionDTO; + sequenceNumber: number; + timestamp: string; + content: string; +} + +export interface AnswerId { + author: string; + toQuestion: QuestionId; + sequenceNumber: number; +} diff --git a/common/src/interfaces/assignment.d.ts b/common/src/interfaces/assignment.d.ts new file mode 100644 index 00000000..8ad1649b --- /dev/null +++ b/common/src/interfaces/assignment.d.ts @@ -0,0 +1,11 @@ +import { GroupDTO } from './group'; + +export interface AssignmentDTO { + id: number; + class: string; // Id of class 'within' + title: string; + description: string; + learningPath: string; + language: string; + groups?: GroupDTO[] | string[]; // TODO +} diff --git a/common/src/interfaces/class.d.ts b/common/src/interfaces/class.d.ts new file mode 100644 index 00000000..c35c2dfc --- /dev/null +++ b/common/src/interfaces/class.d.ts @@ -0,0 +1,7 @@ +export interface ClassDTO { + id: string; + displayName: string; + teachers: string[]; + students: string[]; + joinRequests: string[]; +} diff --git a/common/src/interfaces/group.d.ts b/common/src/interfaces/group.d.ts new file mode 100644 index 00000000..45523461 --- /dev/null +++ b/common/src/interfaces/group.d.ts @@ -0,0 +1,7 @@ +import { AssignmentDTO } from './assignment'; + +export interface GroupDTO { + assignment: number | AssignmentDTO; + groupNumber: number; + members: string[] | StudentDTO[]; +} diff --git a/backend/src/interfaces/learning-content.ts b/common/src/interfaces/learning-content.d.ts similarity index 96% rename from backend/src/interfaces/learning-content.ts rename to common/src/interfaces/learning-content.d.ts index 693aec37..610b1385 100644 --- a/backend/src/interfaces/learning-content.ts +++ b/common/src/interfaces/learning-content.d.ts @@ -1,4 +1,4 @@ -import { Language } from '../entities/content/language'; +import { Language } from 'dwengo-1-backend/src/entities/content/language.js'; export interface Transition { default: boolean; diff --git a/common/src/interfaces/question.d.ts b/common/src/interfaces/question.d.ts new file mode 100644 index 00000000..2b2a263c --- /dev/null +++ b/common/src/interfaces/question.d.ts @@ -0,0 +1,14 @@ +import { StudentDTO } from './student'; + +export interface QuestionDTO { + learningObjectIdentifier: LearningObjectIdentifier; + sequenceNumber?: number; + author: StudentDTO; + timestamp?: string; + content: string; +} + +export interface QuestionId { + learningObjectIdentifier: LearningObjectIdentifier; + sequenceNumber: number; +} diff --git a/common/src/interfaces/student.d.ts b/common/src/interfaces/student.d.ts new file mode 100644 index 00000000..cb13d3c2 --- /dev/null +++ b/common/src/interfaces/student.d.ts @@ -0,0 +1,12 @@ +export interface StudentDTO { + id: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} diff --git a/common/src/interfaces/submission.d.ts b/common/src/interfaces/submission.d.ts new file mode 100644 index 00000000..c4c2b35d --- /dev/null +++ b/common/src/interfaces/submission.d.ts @@ -0,0 +1,19 @@ +import { GroupDTO } from './group'; + +export interface SubmissionDTO { + learningObjectIdentifier: LearningObjectIdentifier; + + submissionNumber?: number; + submitter: StudentDTO; + time?: Date; + group?: GroupDTO; + content: string; +} + +export interface SubmissionDTOId { + learningObjectHruid: string; + learningObjectLanguage: Language; + learningObjectVersion: number; + + submissionNumber?: number; +} diff --git a/common/src/interfaces/teacher-invitation.d.ts b/common/src/interfaces/teacher-invitation.d.ts new file mode 100644 index 00000000..13709322 --- /dev/null +++ b/common/src/interfaces/teacher-invitation.d.ts @@ -0,0 +1,8 @@ +import { UserDTO } from './user'; +import { ClassDTO } from './class'; + +export interface TeacherInvitationDTO { + sender: string | UserDTO; + receiver: string | UserDTO; + class: string | ClassDTO; +} diff --git a/common/src/interfaces/teacher.d.ts b/common/src/interfaces/teacher.d.ts new file mode 100644 index 00000000..05bf74a0 --- /dev/null +++ b/common/src/interfaces/teacher.d.ts @@ -0,0 +1,12 @@ +export interface TeacherDTO { + id: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} diff --git a/common/src/interfaces/user.d.ts b/common/src/interfaces/user.d.ts new file mode 100644 index 00000000..e7bbe2ed --- /dev/null +++ b/common/src/interfaces/user.d.ts @@ -0,0 +1,12 @@ +export interface UserDTO { + id?: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + self: string; + classes: string; + questions: string; + invitations: string; + }; +} diff --git a/common/tsconfig.json b/common/tsconfig.json new file mode 100644 index 00000000..a47827ba --- /dev/null +++ b/common/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "resolveJsonModule": true, + "sourceMap": true, + "composite": true + } +} diff --git a/package-lock.json b/package-lock.json index a38ca88a..52338b8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "workspaces": [ "backend", + "common", "frontend", "docs" ], @@ -40,6 +41,7 @@ "cross": "^1.0.0", "cross-env": "^7.0.3", "dotenv": "^16.4.7", + "dwengo-1-common": "^0.1.1", "express": "^5.0.1", "express-jwt": "^8.5.1", "gift-pegjs": "^1.0.2", @@ -83,6 +85,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "common": { + "name": "dwengo-1-common", + "version": "0.1.1" + }, "docs": { "name": "dwengo-1-docs", "version": "0.0.1", @@ -4919,6 +4925,10 @@ "resolved": "backend", "link": true }, + "node_modules/dwengo-1-common": { + "resolved": "common", + "link": true + }, "node_modules/dwengo-1-docs": { "resolved": "docs", "link": true diff --git a/package.json b/package.json index e23a49f6..c7eeccc3 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,15 @@ "private": true, "type": "module", "scripts": { - "build": "npm run build --workspace=backend --workspace=frontend", - "format": "npm run format --workspace=backend --workspace=frontend", - "format-check": "npm run format-check --workspace=backend --workspace=frontend", - "lint": "npm run lint --workspace=backend --workspace=frontend", + "build": "npm run build --workspaces --if-present", + "format": "npm run format --workspace=backend --workspace=common --workspace=frontend", + "format-check": "npm run format-check --workspace=backend --workspace=common --workspace=frontend", + "lint": "npm run lint --workspace=backend --workspace=common --workspace=frontend", "test:unit": "npm run test:unit --workspace=backend --workspace=frontend" }, "workspaces": [ "backend", + "common", "frontend", "docs" ],