Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [
"fixed": [
[
"@quassel/frontend",
"@quassel/ui"
"@quassel/backend"
]
],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
Expand Down
5 changes: 5 additions & 0 deletions .changeset/forty-crabs-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quassel/backend": patch
---

Add healthchecks
5 changes: 5 additions & 0 deletions .changeset/popular-shrimps-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quassel/backend": patch
---

Add status endpoint
6 changes: 6 additions & 0 deletions .changeset/seven-mirrors-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": patch
"@quassel/backend": patch
---

Add questionnaire management
6 changes: 6 additions & 0 deletions .changeset/sixty-wombats-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": patch
"@quassel/backend": patch
---

Add studies management
6 changes: 6 additions & 0 deletions .changeset/strong-poets-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": patch
"@quassel/backend": patch
---

Add participants management
3 changes: 3 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
"DavidAnson.vscode-markdownlint"
]
}
},
"features": {
// "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
}
4 changes: 3 additions & 1 deletion apps/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN npm install --omit=dev

# Production image, copy all the files and run nest
FROM docker.io/node:20-alpine AS runner
RUN apk add --no-cache dumb-init
RUN apk add --no-cache dumb-init curl
ENV NODE_ENV=production
ENV PORT=3000
WORKDIR /usr/src/app
Expand All @@ -18,4 +18,6 @@ COPY dist/ .
RUN chown -R node:node .
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl --fail http://localhost:3000/health || exit 1
CMD ["dumb-init", "node", "src/main.js"]
12 changes: 7 additions & 5 deletions apps/backend/db/migrations/.snapshot-postgres.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
},
Expand All @@ -293,7 +293,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
},
Expand All @@ -303,7 +303,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"length": 255,
"mappedType": "string"
},
Expand All @@ -322,7 +322,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"mappedType": "integer"
},
"participant_id": {
Expand All @@ -331,7 +331,7 @@
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"nullable": true,
"mappedType": "bigint"
}
},
Expand Down Expand Up @@ -361,6 +361,7 @@
"id"
],
"referencedTableName": "public.study",
"deleteRule": "set null",
"updateRule": "cascade"
},
"questionnaire_participant_id_foreign": {
Expand All @@ -373,6 +374,7 @@
"id"
],
"referencedTableName": "public.participant",
"deleteRule": "set null",
"updateRule": "cascade"
}
},
Expand Down
47 changes: 47 additions & 0 deletions apps/backend/db/migrations/Migration20241108015549.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Migration } from "@mikro-orm/migrations";

export class Migration20241108015549 extends Migration {
override async up(): Promise<void> {
this.addSql(`alter table "questionnaire" drop constraint "questionnaire_study_id_foreign";`);
this.addSql(`alter table "questionnaire" drop constraint "questionnaire_participant_id_foreign";`);

this.addSql(`alter table "questionnaire" alter column "started_at" type timestamptz using ("started_at"::timestamptz);`);
this.addSql(`alter table "questionnaire" alter column "started_at" drop not null;`);
this.addSql(`alter table "questionnaire" alter column "ended_at" type timestamptz using ("ended_at"::timestamptz);`);
this.addSql(`alter table "questionnaire" alter column "ended_at" drop not null;`);
this.addSql(`alter table "questionnaire" alter column "title" type varchar(255) using ("title"::varchar(255));`);
this.addSql(`alter table "questionnaire" alter column "title" drop not null;`);
this.addSql(`alter table "questionnaire" alter column "study_id" type int using ("study_id"::int);`);
this.addSql(`alter table "questionnaire" alter column "study_id" drop not null;`);
this.addSql(`alter table "questionnaire" alter column "participant_id" type bigint using ("participant_id"::bigint);`);
this.addSql(`alter table "questionnaire" alter column "participant_id" drop not null;`);
this.addSql(
`alter table "questionnaire" add constraint "questionnaire_study_id_foreign" foreign key ("study_id") references "study" ("id") on update cascade on delete set null;`
);
this.addSql(
`alter table "questionnaire" add constraint "questionnaire_participant_id_foreign" foreign key ("participant_id") references "participant" ("id") on update cascade on delete set null;`
);
}

override async down(): Promise<void> {
this.addSql(`alter table "questionnaire" drop constraint "questionnaire_study_id_foreign";`);
this.addSql(`alter table "questionnaire" drop constraint "questionnaire_participant_id_foreign";`);

this.addSql(`alter table "questionnaire" alter column "started_at" type timestamptz using ("started_at"::timestamptz);`);
this.addSql(`alter table "questionnaire" alter column "started_at" set not null;`);
this.addSql(`alter table "questionnaire" alter column "ended_at" type timestamptz using ("ended_at"::timestamptz);`);
this.addSql(`alter table "questionnaire" alter column "ended_at" set not null;`);
this.addSql(`alter table "questionnaire" alter column "title" type varchar(255) using ("title"::varchar(255));`);
this.addSql(`alter table "questionnaire" alter column "title" set not null;`);
this.addSql(`alter table "questionnaire" alter column "study_id" type int using ("study_id"::int);`);
this.addSql(`alter table "questionnaire" alter column "study_id" set not null;`);
this.addSql(`alter table "questionnaire" alter column "participant_id" type bigint using ("participant_id"::bigint);`);
this.addSql(`alter table "questionnaire" alter column "participant_id" set not null;`);
this.addSql(
`alter table "questionnaire" add constraint "questionnaire_study_id_foreign" foreign key ("study_id") references "study" ("id") on update cascade;`
);
this.addSql(
`alter table "questionnaire" add constraint "questionnaire_participant_id_foreign" foreign key ("participant_id") references "participant" ("id") on update cascade;`
);
}
}
22 changes: 16 additions & 6 deletions apps/backend/db/seeds/DatabaseSeeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,22 @@ export class DatabaseSeeder extends Seeder {
];

const carers: CarerCreationDto[] = [
{ name: "Grossmutter" },
{ name: "Grossvater" },
{ name: "Mutter" },
{ name: "Vater" },
{ name: "Tante" },
{ name: "Onkel" },
{ name: "Mutter, Mother" },
{ name: "Vater, Father" },
{ name: "Grossmutter, Grandmother" },
{ name: "Grossvater, Grandfather" },
{ name: "Schwester, Sister" },
{ name: "Bruder, Brother" },
{ name: "Tante, Aunt" },
{ name: "Onkel, Uncle" },
{ name: "Onkel, Uncle" },
{ name: "KiTa, Day care centre" },
{ name: "Spielgruppe, Playgroup" },
{ name: "Turnen, Gymnastics" },
{ name: "Kindergarten, Kindergarten" },
{ name: "Hort, After-school care" },
{ name: "Nanny / Babysitter" },
{ name: "Nachbar, Neighbour" },
];

const languages: LanguageCreationDto[] = [
Expand Down
15 changes: 8 additions & 7 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@
"dependencies": {
"@fastify/secure-session": "^7.5.1",
"@fastify/static": "^7.0.4",
"@mikro-orm/cli": "^6.3.13",
"@mikro-orm/core": "^6.3.13",
"@mikro-orm/migrations": "^6.3.13",
"@mikro-orm/cli": "^6.4.0",
"@mikro-orm/core": "^6.4.0",
"@mikro-orm/migrations": "^6.4.0",
"@mikro-orm/nestjs": "^6.0.2",
"@mikro-orm/postgresql": "^6.3.13",
"@mikro-orm/reflection": "^6.3.13",
"@mikro-orm/seeder": "^6.3.13",
"@mikro-orm/postgresql": "^6.4.0",
"@mikro-orm/reflection": "^6.4.0",
"@mikro-orm/seeder": "^6.4.0",
"@nestjs/common": "^10.4.7",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.7",
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-fastify": "^10.4.7",
"@nestjs/swagger": "^8.0.3",
"@nestjs/swagger": "^8.0.5",
"@nestjs/terminus": "^10.2.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"fastify": "^4.28.1",
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/research/participants/participant.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ApiProperty, OmitType, PartialType } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsDate, IsOptional } from "class-validator";
import { IsDateString, IsOptional } from "class-validator";

export class ParticipantDto {
@ApiProperty({ example: 1, description: "The id of the participant (child id)" })
id: number;

@ApiProperty({ example: "2024-11-01T00:05:02.718Z", description: "The birthday of the participant" })
@IsDate()
@IsDateString()
@IsOptional()
birthday?: Date;

Expand Down
12 changes: 6 additions & 6 deletions apps/backend/src/research/questionnaires/questionnaire.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ export class QuestionnaireDto {

@ApiProperty({ example: "2024-11-01T07:00:00.000Z", description: "The starting date of the questionnaire" })
@IsDate()
startedAt: Date;
startedAt?: Date;

@ApiProperty({ example: "2024-11-01T08:00:00.00Z", description: "The ending date of the questionnaire" })
@IsDate()
endedAt: Date;
endedAt?: Date;

@ApiProperty({ example: "First few months", description: "The title of the questionnaire" })
@IsNotEmpty()
title: string;
title?: string;

@ApiProperty({ example: "We went on holidays for 2 weeks and only spoke Esperanto", description: "The remark of the questionnaire" })
remark?: string;

@Type(() => StudyDto)
study: StudyDto;
study?: StudyDto;

@Type(() => ParticipantDto)
participant: ParticipantDto;
participant?: ParticipantDto;

@Type(() => Array<number>)
entries: number[];
entries?: number[];
}
export class QuestionnaireResponseDto extends QuestionnaireDto {}
export class QuestionnaireCreationDto extends OmitType(QuestionnaireDto, ["id"]) {}
Expand Down
10 changes: 5 additions & 5 deletions apps/backend/src/research/questionnaires/questionnaire.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import { Entry } from "../entries/entry.entity";
@Entity()
export class Questionnaire extends BaseEntity {
@Property()
startedAt!: Date;
startedAt?: Date;

@Property()
endedAt!: Date;
endedAt?: Date;

@Property()
title!: string;
title?: string;

@Property({ columnType: "text" })
remark?: string;

@ManyToOne()
study!: Study;
study?: Study;

@ManyToOne()
participant!: Participant;
participant?: Participant;

@OneToMany(() => Entry, (entry) => entry.questionnaire)
entries = new Collection<Entry>(this);
Expand Down
30 changes: 15 additions & 15 deletions apps/backend/src/research/studies/studies.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,48 @@
export class StudiesService {
constructor(
@InjectRepository(Study)
private readonly questionnaireRepository: EntityRepository<Study>,
private readonly studyRepository: EntityRepository<Study>,
private readonly em: EntityManager
) {}

async create(questionnaireCreationDto: StudyCreationDto) {
const questionnaire = new Study();
questionnaire.assign(questionnaireCreationDto);
async create(studyCreationDto: StudyCreationDto) {
const study = new Study();
study.assign(studyCreationDto);

Check warning on line 17 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L15-L17

Added lines #L15 - L17 were not covered by tests

try {
await this.em.persist(questionnaire).flush();
await this.em.persist(study).flush();

Check warning on line 20 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L20

Added line #L20 was not covered by tests
} catch (e) {
if (e instanceof UniqueConstraintViolationException) {
throw new UnprocessableEntityException("Study with this name already exists");
}
throw e;
}

return questionnaire.toObject();
return study.toObject();

Check warning on line 28 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L28

Added line #L28 was not covered by tests
}

async findAll() {
return (await this.questionnaireRepository.findAll()).map((questionnaire) => questionnaire.toObject());
return (await this.studyRepository.findAll()).map((study) => study.toObject());

Check warning on line 32 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L32

Added line #L32 was not covered by tests
}

async findOne(id: number) {
return (await this.questionnaireRepository.findOneOrFail(id)).toObject();
return (await this.studyRepository.findOneOrFail(id)).toObject();

Check warning on line 36 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L36

Added line #L36 was not covered by tests
}

async findBy(filter: FilterQuery<Study>) {
return (await this.questionnaireRepository.findOneOrFail(filter)).toObject();
return (await this.studyRepository.findOneOrFail(filter)).toObject();

Check warning on line 40 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L40

Added line #L40 was not covered by tests
}

async update(id: number, questionnaireMutationDto: StudyMutationDto) {
const questionnaire = await this.questionnaireRepository.findOneOrFail(id);
questionnaire.assign(questionnaireMutationDto);
async update(id: number, studyMutationDto: StudyMutationDto) {
const study = await this.studyRepository.findOneOrFail(id);
study.assign(studyMutationDto);

Check warning on line 45 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L43-L45

Added lines #L43 - L45 were not covered by tests

await this.em.persist(questionnaire).flush();
await this.em.persist(study).flush();

Check warning on line 47 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L47

Added line #L47 was not covered by tests

return questionnaire.toObject();
return study.toObject();

Check warning on line 49 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L49

Added line #L49 was not covered by tests
}

remove(id: number) {
return this.em.remove(this.questionnaireRepository.getReference(id)).flush();
return this.em.remove(this.studyRepository.getReference(id)).flush();

Check warning on line 53 in apps/backend/src/research/studies/studies.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/studies/studies.service.ts#L53

Added line #L53 was not covered by tests
}
}
Loading