Skip to content

Commit

Permalink
Merge branch 'master' of github.com:parlemonde/1village into publish
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-pages committed Oct 16, 2024
2 parents 9aeff9c + d8b9922 commit 68405f5
Show file tree
Hide file tree
Showing 128 changed files with 1,333 additions and 964 deletions.
Empty file modified .pnp.cjs
100755 → 100644
Empty file.
Empty file modified .yarn/sdks/eslint/bin/eslint.js
100755 → 100644
Empty file.
Empty file modified .yarn/sdks/prettier/bin-prettier.js
100755 → 100644
Empty file.
Empty file modified .yarn/sdks/typescript/bin/tsc
100755 → 100644
Empty file.
Empty file modified .yarn/sdks/typescript/bin/tsserver
100755 → 100644
Empty file.
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
version: '3.7'
services:
backend:
build:
Expand Down
101 changes: 56 additions & 45 deletions server/controllers/statistics.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,56 @@
import type { Request } from 'express';

import {
getClassroomsInfos,
getConnectedClassroomsCount,
getContributedClassroomsCount,
getRegisteredClassroomsCount,
} from '../stats/classroomStats';
import {
getAverageConnections,
getAverageDuration,
getMaxConnections,
getMaxDuration,
getMedianConnections,
getMedianDuration,
getMinConnections,
getMinDuration,
} from '../stats/sessionStats';
import { Controller } from './controller';

export const statisticsController = new Controller('/statistics');

statisticsController.get({ path: '/sessions/:phase' }, async (req: Request, res) => {
const phase = req.params.phase ? parseInt(req.params.phase) : null;

res.sendJSON({
minDuration: await getMinDuration(), // TODO - add phase
maxDuration: await getMaxDuration(), // TODO - add phase
averageDuration: await getAverageDuration(), // TODO - add phase
medianDuration: await getMedianDuration(), // TODO - add phase
minConnections: await getMinConnections(), // TODO - add phase
maxConnections: await getMaxConnections(), // TODO - add phase
averageConnections: await getAverageConnections(), // TODO - add phase
medianConnections: await getMedianConnections(), // TODO - add phase
registeredClassroomsCount: await getRegisteredClassroomsCount(),
connectedClassroomsCount: await getConnectedClassroomsCount(), // TODO - add phase
contributedClassroomsCount: await getContributedClassroomsCount(phase),
});
});

statisticsController.get({ path: '/classrooms' }, async (_req, res) => {
res.sendJSON({
classrooms: await getClassroomsInfos(),
});
});
import type { Request } from 'express';

import {
getClassroomsInfos,
getConnectedClassroomsCount,
getContributedClassroomsCount,
getRegisteredClassroomsCount,
} from '../stats/classroomStats';
import {
getAverageConnections,
getAverageDuration,
getMaxConnections,
getMaxDuration,
getMedianConnections,
getMedianDuration,
getMinConnections,
getMinDuration,
} from '../stats/sessionStats';
import { getChildrenCodesCount, getFamilyAccountsCount, getConnectedFamiliesCount, getFamiliesWithoutAccount } from '../stats/villageStats';
import { Controller } from './controller';

export const statisticsController = new Controller('/statistics');

statisticsController.get({ path: '/sessions/:phase' }, async (req: Request, res) => {
const phase = req.params.phase ? parseInt(req.params.phase) : null;

res.sendJSON({
minDuration: await getMinDuration(), // TODO - add phase
maxDuration: await getMaxDuration(), // TODO - add phase
averageDuration: await getAverageDuration(), // TODO - add phase
medianDuration: await getMedianDuration(), // TODO - add phase
minConnections: await getMinConnections(), // TODO - add phase
maxConnections: await getMaxConnections(), // TODO - add phase
averageConnections: await getAverageConnections(), // TODO - add phase
medianConnections: await getMedianConnections(), // TODO - add phase
registeredClassroomsCount: await getRegisteredClassroomsCount(),
connectedClassroomsCount: await getConnectedClassroomsCount(), // TODO - add phase
contributedClassroomsCount: await getContributedClassroomsCount(phase),
});
});

statisticsController.get({ path: '/classrooms' }, async (_req, res) => {
res.sendJSON({
classrooms: await getClassroomsInfos(),
});
});

statisticsController.get({ path: '/villages/:villageId' }, async (_req, res) => {
const villageId = parseInt(_req.params.villageId);
res.sendJSON({
familyAccountsCount: await getFamilyAccountsCount(villageId),
childrenCodesCount: await getChildrenCodesCount(villageId),
connectedFamiliesCount: await getConnectedFamiliesCount(villageId),
familiesWithoutAccount: await getFamiliesWithoutAccount(villageId),
});
});
19 changes: 17 additions & 2 deletions server/controllers/village.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@ const villageController = new Controller('/villages');

//--- Get all villages ---
villageController.get({ path: '', userType: UserType.OBSERVATOR }, async (_req: Request, res: Response) => {
const villages = await AppDataSource.getRepository(Village).find();
res.sendJSON(villages);
const countryIsoCode = _req.query.countryIsoCode as string;
try {
const villageRepository = AppDataSource.getRepository(Village);
let villages;
if (countryIsoCode) {
const query = villageRepository.createQueryBuilder('village');
query.where('village.countryCodes LIKE :countryIsoCode', { countryIsoCode: `%${countryIsoCode.toUpperCase()}%` });

villages = await query.getMany();
} else {
villages = await villageRepository.find();
}
res.sendJSON(villages);
} catch (e) {
console.error(e);
res.status(500).sendJSON({ message: 'An error occurred while fetching villages' });
}
});

//--- Get one village ---
Expand Down
2 changes: 0 additions & 2 deletions server/stats/classroomStats.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { UserType } from '../../types/user.type';
import { Activity } from '../entities/activity';
import { Classroom } from '../entities/classroom';
// import { Comment } from '../entities/comment';
// import { Video } from '../entities/video';
import { AppDataSource } from '../utils/data-source';

const classroomRepository = AppDataSource.getRepository(Classroom);
Expand Down
60 changes: 60 additions & 0 deletions server/stats/villageStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Student } from '../entities/student';
import { User } from '../entities/user';
import { AppDataSource } from '../utils/data-source';

const userRepository = AppDataSource.getRepository(User);
const studentRepository = AppDataSource.getRepository(Student);

export const getChildrenCodesCount = async (villageId: number) => {
const childrenCodeCount = await studentRepository
.createQueryBuilder('student')
.innerJoin('student.classroom', 'classroom')
.innerJoin('classroom.village', 'village')
.where('classroom.villageId = :villageId', { villageId })
.getCount();
return childrenCodeCount;
};

export const getFamilyAccountsCount = async (villageId: number) => {
const familyAccountsCount = await userRepository
.createQueryBuilder('user')
.innerJoin('user.village', 'village')
.innerJoin('classroom', 'classroom', 'classroom.villageId = village.id')
.innerJoin('student', 'student', 'student.classroomId = classroom.id')
.where('classroom.villageId = :villageId', { villageId })
.groupBy('user.id')
.getCount();
return familyAccountsCount;
};

export const getConnectedFamiliesCount = async (villageId: number) => {
const connectedFamiliesCount = await studentRepository
.createQueryBuilder('student')
.innerJoin('classroom', 'classroom', 'classroom.id = student.classroomId')
.where('classroom.villageId = :villageId', { villageId })
.andWhere('student.numLinkedAccount >= 1')
.getCount();

return connectedFamiliesCount;
};

export const getFamiliesWithoutAccount = async (villageId: number) => {
const familiesWithoutAccount = await studentRepository
.createQueryBuilder('student')
.innerJoin('student.classroom', 'classroom')
.innerJoin('classroom.user', 'user')
.innerJoin('user.village', 'village')
.where('classroom.villageId = :villageId', { villageId })
.andWhere('student.numLinkedAccount < 1')
.select([
'classroom.name AS classroom_name',
'classroom.countryCode as classroom_country',
'student.firstname AS student_firstname',
'student.lastname AS student_lastname',
'student.id AS student_id',
'village.name AS village_name',
])
.getRawMany();

return familiesWithoutAccount;
};
26 changes: 13 additions & 13 deletions src/activity-types/defi.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { capitalize, replaceTokens } from 'src/utils';
export const COOKING_DEFIS = [
{
title: 'Réalisez notre recette à votre tour',
description: 'Les Pelicopains devront créer une présentation sous forme de texte, son, image ou une vidéo',
description: 'Les pélicopains devront créer une présentation sous forme de texte, son, image ou une vidéo',
},
{
title: 'Présentez-nous une de vos recettes traditionnelles',
description: 'Les Pelicopains devront créer une présentation sous forme de texte, son, image ou une vidéo',
description: 'Les pélicopains devront créer une présentation sous forme de texte, son, image ou une vidéo',
},
];
export const ECO_ACTIONS = [
Expand All @@ -39,8 +39,8 @@ export const ECO_DEFIS = [
},
];
export const LANGUAGE_SCHOOL = [
'maternelle chez tous les élèves',
'maternelle chez certains élèves',
'maternelle chez tous les enfants',
'maternelle chez certains enfants',
'qu’on utilise pour faire cours',
'qu’on apprend comme langue étrangère',
];
Expand All @@ -49,50 +49,50 @@ export const LANGUAGE_THEMES = [
title: 'Un mot précieux',
title2: 'le mot précieux',
desc1: "Choisissez un mot 'précieux' qui a quelque chose d'original (prononciation,origine...) dans la langue {{language}}.",
desc2: 'Expliquez pourquoi vous avez choisi ce mot précieux, ce qu’il signifie et quand vous l’utilisez.',
desc2: 'Ecrivez votre mot précieux puis expliquez pourquoi avoir choisi celui-ci, ce qu’il signifie et quand vous l’utilisez.',
},
{
title: 'Une expression',
title2: "l'expression",
desc1: 'Choisissez une expression surprenante dans la langue {{language}}.',
desc2: 'Expliquez pourquoi vous avez choisi cette expression, ce qu’elle signifie et quand vous l’utilisez.',
desc2: 'Ecrivez votre expression puis expliquez pourquoi avoir choisi celle-ci, ce qu’elle signifie et quand vous l’utilisez.',
},
{
title: 'Une poésie',
title2: 'la poésie',
desc1: 'Choisissez une poésie écrite dans la langue {{language}}.',
desc2: 'Expliquez pourquoi vous avez choisi cette poésie, ce qu’elle signifie et quand vous l’utilisez.',
desc2: 'Ecrivez votre poésie puis expliquez pourquoi avoir choisi celle-ci, ce qu’elle signifie et quand vous l’utilisez.',
},
{
title: 'Une chanson',
title2: 'la chanson',
desc1: 'Choisissez une chanson écrite dans la langue {{language}}.',
desc2: 'Expliquez pourquoi vous avez choisi cette chanson, ce qu’elle signifie et quand vous l’utilisez.',
desc2: 'Ecrivez votre chanson puis expliquez pourquoi avoir choisi celle-ci, ce qu’elle signifie et quand vous l’utilisez.',
},
];
export const LANGUAGE_DEFIS = [
{
title: 'Trouvez {{theme}} similaire dans une autre langue',
description: 'Les Pelicopains devront envoyer un texte, un son ou une vidéo.',
description: 'Les pélicopains devront envoyer un texte, un son ou une vidéo.',
},
{
title: 'Répétez à l’oral {{theme}} en {{language}}',
description: 'Les Pelicopains devront envoyer un son ou une vidéo.',
description: 'Les pélicopains devront envoyer un son ou une vidéo.',
},
{
title: 'Écrivez {{theme}} en {{language}}',
description: 'Les Pelicopains devront envoyer un texte, une image ou une vidéo.',
description: 'Les pélicopains devront envoyer un texte, une image ou une vidéo.',
},
];

export const FREE_DEFIS = [
{
title: 'Réalisez notre action à votre tour',
description: 'Les Pelicopains devront réaliser la même action que vous',
description: 'Les pélicopains devront réaliser la même action que vous',
},
{
title: 'Réalisez une autre action sur le même thème',
description: 'Les Pelicopains devront réaliser une action sur le thème {{theme}}',
description: 'Les pélicopains devront réaliser une action sur le thème {{theme}}',
},
];
export const DEFI = {
Expand Down
8 changes: 4 additions & 4 deletions src/activity-types/enigme.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ENIGME_TYPES = [
titleStep1: 'Choisissez votre objet mystère',
titleStep2: 'Décrivez votre objet mystère',
titleStep2Short: 'objet',
description: `La classe choisit de présenter un "objet mystère", à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cet objet peut-être...`,
description: `Choisissez un "objet mystère" à présenter aux pélicopains à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cet objet peut-être...`,
subCategories: [
{
label: 'Un jouet',
Expand Down Expand Up @@ -52,7 +52,7 @@ export const ENIGME_TYPES = [
titleStep1: 'Choisissez votre événement mystère',
titleStep2: 'Décrivez votre événement mystère',
titleStep2Short: 'événement',
description: `La classe choisit de présenter un "événement mystère", à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cet événement peut-être une fête de l’école, un festival, un fait historique s'étant déroulé localement, une fête nationale...`,
description: `Choisissez un "événement mystère" à présenter aux pélicopains à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cet événement peut-être...`,
subCategories: [
{
label: 'Une fête de l’école',
Expand Down Expand Up @@ -83,7 +83,7 @@ export const ENIGME_TYPES = [
titleStep1: 'Choisissez votre personnalité mystère',
titleStep2: 'Décrivez votre personnalité mystère',
titleStep2Short: 'personnalité',
description: `La classe choisit de présenter une "personnalité mystère", à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cette personnalité peut-être...`,
description: `Choisissez une "personnalité mystère" à présenter aux pélicopains à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Cette personnalité peut-être...`,
subCategories: [
{
label: 'Un personnage historique',
Expand All @@ -109,7 +109,7 @@ export const ENIGME_TYPES = [
titleStep1: 'Choisissez votre lieu mystère',
titleStep2: 'Décrivez votre lieu mystère',
titleStep2Short: 'lieu',
description: `La classe choisit de présenter un "lieu mystère", à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Ce lieu peut-être...`,
description: `Choisissez un "lieu mystère" à présenter aux pélicopains à travers une énigme sous forme de texte, de son, d'image ou de vidéo. Ce lieu peut-être...`,
subCategories: [
{
label: 'Un monument commémoratif',
Expand Down
12 changes: 6 additions & 6 deletions src/activity-types/mascotte.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const getMascotteContent = (data: MascotteData, countries: Country[], cur
data.girlStudent ?? 0
} fille${pluralS(data.girlStudent || 0)} et ${data.boyStudent ?? 0} garçon${pluralS(
data.boyStudent || 0,
)}.\nEn moyenne, l’âge des élèves de notre classe est ${data.meanAge ?? 0} an${pluralS(data.meanAge || 0)}.\nNous avons ${
)}.\nEn moyenne, l’âge des enfants de notre classe est ${data.meanAge ?? 0} an${pluralS(data.meanAge || 0)}.\nNous avons ${
data.totalTeacher ?? 0
} professeur${pluralS(data.totalTeacher || 0)}, dont ${data.womanTeacher ?? 0} femme${pluralS(data.womanTeacher || 0)} et ${
data.manTeacher ?? 0
Expand All @@ -66,13 +66,13 @@ export const getMascotteContent = (data: MascotteData, countries: Country[], cur
content.push(
`${
displayFluentLanguages.length > 0
? 'Tous les élèves de notre classe parlent : ' + naturalJoin(displayFluentLanguages) + '.'
: 'Les élèves de notre classe ne parlent aucune langue !'
? 'Tous les enfants de notre classe parlent : ' + naturalJoin(displayFluentLanguages) + '.'
: 'Les enfants de notre classe ne parlent aucune langue !'
}\n${
displayMinorLanguages.length > 0
? 'Au moins un élève de notre classe parle: ' + naturalJoin(displayMinorLanguages) + '.'
: 'Aucun élève de notre classe ne parle de langue supplémentaire.'
}\n${capitalize(data.mascotteName)}, comme tous les élèves de notre classe, ${
? 'Au moins un enfant de notre classe parle: ' + naturalJoin(displayMinorLanguages) + '.'
: 'Aucun enfant de notre classe ne parle de langue supplémentaire.'
}\n${capitalize(data.mascotteName)}, comme tous les enfants de notre classe, ${
displayWantedLanguages.length > 0 ? ' apprend : ' + naturalJoin(displayWantedLanguages) + '.' : " n'apprend aucune langue."
}\nNous ${displayCurrencies.length > 0 ? ' utilisons comme monnaie : ' + naturalJoin(displayCurrencies) + '.' : " n'utilisons aucune monnaie."}`,
);
Expand Down
Loading

0 comments on commit 68405f5

Please sign in to comment.