From be3963de6b07c65cac74db1a1ba72754758fcc96 Mon Sep 17 00:00:00 2001 From: Joao Pedro da Silva Date: Tue, 2 May 2023 15:10:38 -0300 Subject: [PATCH 1/5] update lessons and levels control --- .../api/src/controllers/v2/learnAndEarn.ts | 31 +-- packages/api/src/routes/v2/learnAndEarn.ts | 6 +- packages/api/src/validators/learnAndEarn.ts | 6 +- ...661374519-create-learnAndEarnUserLesson.js | 5 - ...682361632-update-learnAndEarnUserLesson.js | 26 ++ ...682521792-update-learnAndEarnUserLesson.js | 51 ++++ .../models/associations/learnAndEarn.ts | 27 ++ .../learnAndEarn/learnAndEarnUserLesson.ts | 10 +- .../learnAndEarn/learnAndEarnPrismicLesson.ts | 4 + .../learnAndEarn/learnAndEarnUserLesson.ts | 4 + .../core/src/services/learnAndEarn/list.ts | 262 +++++++++--------- .../core/src/services/learnAndEarn/start.ts | 35 +-- .../src/services/learnAndEarn/syncRemote.ts | 23 +- .../src/services/learnAndEarn/userData.ts | 16 +- 14 files changed, 302 insertions(+), 204 deletions(-) create mode 100644 packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js create mode 100644 packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js diff --git a/packages/api/src/controllers/v2/learnAndEarn.ts b/packages/api/src/controllers/v2/learnAndEarn.ts index 16e58a389..963f80211 100644 --- a/packages/api/src/controllers/v2/learnAndEarn.ts +++ b/packages/api/src/controllers/v2/learnAndEarn.ts @@ -32,17 +32,7 @@ class LearnAndEarnController { req: RequestWithUser, res: Response ) => { - if (req.user === undefined) { - standardResponse(res, 401, false, '', { - error: { - name: 'USER_NOT_FOUND', - message: 'User not identified!', - }, - }); - return; - } - - let { category, status, level, limit, offset } = req.query; + let { category, status, level, limit, offset, language } = req.query; if (offset === undefined || typeof offset !== 'string') { offset = config.defaultOffset.toString(); @@ -53,12 +43,13 @@ class LearnAndEarnController { services.learnAndEarn .listLevels( - req.user.userId, parseInt(offset, 10), parseInt(limit, 10), + req.user?.userId, status, category, - level + level, + language, ) .then((r) => standardResponse(res, 200, true, r)) .catch((e) => standardResponse(res, 400, false, '', { error: e })); @@ -104,18 +95,12 @@ class LearnAndEarnController { }; listLessons = (req: RequestWithUser<{ id: string }>, res: Response) => { - if (req.user === undefined) { - standardResponse(res, 401, false, '', { - error: { - name: 'USER_NOT_FOUND', - message: 'User not identified!', - }, - }); - return; - } + let { language } = req.query; + + const id = parseInt(req.params.id, 10); services.learnAndEarn - .listLessons(req.user.userId, parseInt(req.params.id, 10)) + .listLessons(id ? id : req.params.id, req.user?.userId, language as any) .then((r) => standardResponse(res, 200, true, r)) .catch((e) => standardResponse(res, 400, false, '', { error: e })); }; diff --git a/packages/api/src/routes/v2/learnAndEarn.ts b/packages/api/src/routes/v2/learnAndEarn.ts index 0d355d8f8..91b9f8ba2 100644 --- a/packages/api/src/routes/v2/learnAndEarn.ts +++ b/packages/api/src/routes/v2/learnAndEarn.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import LearnAndEarnController from '../../controllers/v2/learnAndEarn'; -import { authenticateToken } from '../../middlewares'; +import { authenticateToken, optionalAuthentication } from '../../middlewares'; import LearnAndEarnValidator from '../../validators/learnAndEarn'; export default (app: Router): void => { @@ -47,7 +47,7 @@ export default (app: Router): void => { */ route.get( '/levels', - authenticateToken, + optionalAuthentication, learnAndEarnValidator.listLevels, learnAndEarnController.listLevels ); @@ -125,7 +125,7 @@ export default (app: Router): void => { */ route.get( '/levels/:id', - authenticateToken, + optionalAuthentication, learnAndEarnController.listLessons ); diff --git a/packages/api/src/validators/learnAndEarn.ts b/packages/api/src/validators/learnAndEarn.ts index ca14f4148..467ddab1a 100644 --- a/packages/api/src/validators/learnAndEarn.ts +++ b/packages/api/src/validators/learnAndEarn.ts @@ -10,7 +10,7 @@ export type AnswerRequestType = { answers: number[]; }; export type StartLessonRequestType = { - lesson: number; + lesson: string; }; export type ListLevelsRequestType = { status: string; @@ -18,6 +18,7 @@ export type ListLevelsRequestType = { level: string; limit?: string; offset?: string; + language?: string; }; export type RegisterClaimRewardsRequestType = { transactionHash: string; @@ -36,7 +37,7 @@ class LearnAndEarnValidator { startLesson = celebrate({ body: defaultSchema.object({ - lesson: Joi.number().required(), + lesson: Joi.string().required(), }), }); @@ -47,6 +48,7 @@ class LearnAndEarnValidator { .valid('available', 'started', 'completed'), category: Joi.string().optional(), level: Joi.string().optional(), + language: Joi.string().optional(), }, }); diff --git a/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js index 3988b8fe6..02655dedd 100644 --- a/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js +++ b/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js @@ -21,11 +21,6 @@ module.exports = { }, lessonId: { type: Sequelize.INTEGER, - references: { - model: 'learn_and_earn_lesson', - key: 'id', - }, - onDelete: 'CASCADE', allowNull: false, }, status: { diff --git a/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js new file mode 100644 index 000000000..f7f18d031 --- /dev/null +++ b/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js @@ -0,0 +1,26 @@ +'use strict'; + +// eslint-disable-next-line no-undef +module.exports = { + async up(queryInterface, Sequelize) { + // add new columns + await queryInterface.addColumn('learn_and_earn_user_lesson', 'levelId', { + type: Sequelize.INTEGER, + references: { + model: 'learn_and_earn_level', + key: 'id', + }, + onDelete: 'CASCADE', + allowNull: true, + }); + + await queryInterface.removeConstraint('learn_and_earn_user_lesson', 'learn_and_earn_user_lesson_lessonId_fkey'); + + // set old columns as optional + await queryInterface.changeColumn('learn_and_earn_user_lesson', 'lessonId', { + type: Sequelize.INTEGER, + allowNull: false, + }); + }, + down: (queryInterface) => {}, +}; diff --git a/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js new file mode 100644 index 000000000..6ec111af7 --- /dev/null +++ b/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js @@ -0,0 +1,51 @@ +'use strict'; + +const prismic = require('@prismicio/client'); + +const endpoint = prismic.getEndpoint(process.env.PRISMIC_REPO); +const accessToken = process.env.PRISMIC_ACCESS_TOKEN; + +const client = prismic.createClient(endpoint, { accessToken }); + +// eslint-disable-next-line no-undef +module.exports = { + async up(queryInterface, Sequelize) { + // get all user lesson + const userLessons = await queryInterface.sequelize.query( + `select learn_and_earn_user_lesson.id as id, learn_and_earn_lesson."prismicId" as "prismicId", learn_and_earn_lesson."levelId" as "levelId" + from learn_and_earn_user_lesson inner join learn_and_earn_lesson on learn_and_earn_user_lesson."lessonId" = learn_and_earn_lesson.id + where learn_and_earn_user_lesson."levelId" is null`, + { type: Sequelize.QueryTypes.SELECT } + ); + + // get prismic id + const prismicIds = userLessons.map((userLesson) => userLesson.prismicId); + + // get prismic document + try { + const prismicData = await client.getAllByIDs(prismicIds); + + userLessons.forEach(async (userLesson) => { + const prismic = prismicData.find( + (el) => el.id === userLesson.prismicId + ); + const levelId = userLesson.levelId; + const lessonId = prismic.data.id; + + if (!!levelId && !!lessonId) { + await queryInterface.sequelize.query( + `update learn_and_earn_user_lesson set "levelId" = ${levelId}, "lessonId" = ${lessonId} where id = ${userLesson.id}` + ); + } + }); + + // throw new Error(); + + } catch (error) { + console.log(error); + throw new Error(); + + } + }, + down: (queryInterface) => {}, +}; diff --git a/packages/core/src/database/models/associations/learnAndEarn.ts b/packages/core/src/database/models/associations/learnAndEarn.ts index 0ac415d7a..00d13612d 100644 --- a/packages/core/src/database/models/associations/learnAndEarn.ts +++ b/packages/core/src/database/models/associations/learnAndEarn.ts @@ -10,6 +10,33 @@ export function learnAndEarnAssociation(sequelize: Sequelize) { } ); + sequelize.models.LearnAndEarnPrismicLevelModel.hasMany( + sequelize.models.LearnAndEarnUserLevelModel, + { + foreignKey: 'levelId', + sourceKey: 'levelId', + as: 'userLevel', + } + ); + + sequelize.models.LearnAndEarnPrismicLevelModel.hasMany( + sequelize.models.LearnAndEarnPrismicLessonModel, + { + foreignKey: 'levelId', + sourceKey: 'levelId', + as: 'lesson', + } + ); + + sequelize.models.LearnAndEarnPrismicLessonModel.hasMany( + sequelize.models.LearnAndEarnUserLessonModel, + { + foreignKey: 'levelId', + sourceKey: 'levelId', + as: 'userLesson', + } + ); + sequelize.models.LearnAndEarnLessonModel.hasMany( sequelize.models.LearnAndEarnUserLessonModel, { diff --git a/packages/core/src/database/models/learnAndEarn/learnAndEarnUserLesson.ts b/packages/core/src/database/models/learnAndEarn/learnAndEarnUserLesson.ts index 96388cc16..11023eb6f 100644 --- a/packages/core/src/database/models/learnAndEarn/learnAndEarnUserLesson.ts +++ b/packages/core/src/database/models/learnAndEarn/learnAndEarnUserLesson.ts @@ -12,6 +12,7 @@ export class LearnAndEarnUserLessonModel extends Model< public id!: number; public userId!: number; public lessonId!: number; + public levelId!: number; public status!: 'available' | 'started' | 'completed'; public completionDate!: Date; public attempts!: number; @@ -40,11 +41,10 @@ export function initializeLearnAndEarnUserLesson( }, lessonId: { type: DataTypes.INTEGER, - references: { - model: 'learn_and_earn_lesson', - key: 'id', - }, - onDelete: 'CASCADE', + allowNull: false, + }, + levelId: { + type: DataTypes.INTEGER, allowNull: false, }, status: { diff --git a/packages/core/src/interfaces/learnAndEarn/learnAndEarnPrismicLesson.ts b/packages/core/src/interfaces/learnAndEarn/learnAndEarnPrismicLesson.ts index e2caf3b82..c194b3679 100644 --- a/packages/core/src/interfaces/learnAndEarn/learnAndEarnPrismicLesson.ts +++ b/packages/core/src/interfaces/learnAndEarn/learnAndEarnPrismicLesson.ts @@ -1,3 +1,5 @@ +import { LearnAndEarnUserLessonModel } from '../../database/models/learnAndEarn/learnAndEarnUserLesson'; + /** * @swagger * components: @@ -29,6 +31,8 @@ export interface LearnAndEarnPrismicLesson { lessonId: number; language: string; isLive: boolean; + + userLesson?: LearnAndEarnUserLessonModel[]; } export interface LearnAndEarnPrismicLessonCreation { diff --git a/packages/core/src/interfaces/learnAndEarn/learnAndEarnUserLesson.ts b/packages/core/src/interfaces/learnAndEarn/learnAndEarnUserLesson.ts index a5c77a1c7..e85d07c5a 100644 --- a/packages/core/src/interfaces/learnAndEarn/learnAndEarnUserLesson.ts +++ b/packages/core/src/interfaces/learnAndEarn/learnAndEarnUserLesson.ts @@ -19,6 +19,8 @@ * type: integer * lessonId: * type: integer + * levelId: + * type: integer * status: * type: string * completionDate: @@ -32,6 +34,7 @@ export interface LearnAndEarnUserLesson { id: number; userId: number; lessonId: number; + levelId: number; status: 'available' | 'started' | 'completed'; completionDate: Date; attempts: number; @@ -41,6 +44,7 @@ export interface LearnAndEarnUserLesson { export interface LearnAndEarnUserLessonCreation { userId: number; lessonId: number; + levelId: number; status: 'available' | 'started' | 'completed'; completionDate?: Date; attempts?: number; diff --git a/packages/core/src/services/learnAndEarn/list.ts b/packages/core/src/services/learnAndEarn/list.ts index d6441743c..0dad8fa38 100644 --- a/packages/core/src/services/learnAndEarn/list.ts +++ b/packages/core/src/services/learnAndEarn/list.ts @@ -1,19 +1,19 @@ -import { literal, Op, WhereOptions, fn, col } from 'sequelize'; +import { literal, Op, WhereOptions, fn, col, Includeable, ProjectionAlias } from 'sequelize'; import { models } from '../../database'; -import { LearnAndEarnLesson } from '../../interfaces/learnAndEarn/learnAndEarnLesson'; -import { LearnAndEarnLevel } from '../../interfaces/learnAndEarn/learnAndEarnLevel'; +import { LearnAndEarnPrismicLevel } from '../../interfaces/learnAndEarn/learnAndEarnPrismicLevel'; +import { LearnAndEarnPrismicLesson } from '../../interfaces/learnAndEarn/learnAndEarnPrismicLesson'; import { formatObjectToNumber } from '../../utils'; import { BaseError } from '../../utils/baseError'; import config from '../../config'; export async function listLevels( - userId: number, offset: number, limit: number, + userId?: number, status?: string, - category?: string, - level?: string + level?: string, + language?: string, ): Promise<{ count: number; rows: { @@ -25,114 +25,100 @@ export async function listLevels( }[]; }> { try { - let whereCategory = {}; - if (category) { - whereCategory = { + const userQuery: { + attributes: ProjectionAlias[]; + include: Includeable[]; + group: string[]; + } = { + attributes: [], + include: [], + group: [], + }; + + if (userId) { + const user = await models.appUser.findOne({ + attributes: ['language'], + where: { id: userId }, + }); + language = user?.language; + userQuery.include.push({ + attributes: [], + model: models.learnAndEarnUserLevel, + as: 'userLevel', + required: false, where: { - prismicId: category, + userId, }, - }; + duplicating: false, + }); + userQuery.attributes.push( + [literal('"userLevel".status'), 'status'] + ); + userQuery.group.push('"userLevel".status'); } - const user = await models.appUser.findOne({ - attributes: ['language'], - where: { id: userId }, - }); - const where: WhereOptions = { + if (!language) { + throw new BaseError('LANGUAGE_NOT_FOUND', 'language not found'); + } + + const where: WhereOptions = { [Op.and]: [ - // TODO: sanitize this status - ? literal( - status === 'available' - ? `("userLevel".status = 'started' or "userLevel".status is null)` - : `"userLevel".status = '${status}'` - ) + ? status === 'available' + ? { + [Op.or]: [ + { '$userLevel.status$': 'started' }, + { '$userLevel.status$': null }, + ], + } + : { '$userLevel.status$': status } : {}, level ? { prismicId: level } : {}, - { languages: { [Op.contains]: [user!.language] } }, - { active: true }, + { language }, process.env.API_ENVIRONMENT === 'production' ? { isLive: true } : {}, ], }; - const userLevels = (await models.learnAndEarnLevel.findAll({ + + + const userLevels = (await models.learnAndEarnPrismicLevel.findAll({ attributes: [ - 'id', + 'levelId', 'prismicId', - 'totalReward', - [literal('"userLevel".status'), 'status'], [literal(`count(lesson.id)`), 'totalLessons'], + ...userQuery.attributes, ], include: [ + ...userQuery.include, { attributes: [], - model: models.learnAndEarnUserLevel, - as: 'userLevel', - required: false, - where: { - userId, - }, - duplicating: false, - }, - { - attributes: [], - model: models.learnAndEarnLesson, + model: models.learnAndEarnPrismicLesson, as: 'lesson', duplicating: false, - where: { - active: true, - } - }, - { - attributes: ['prismicId'], - model: models.learnAndEarnCategory, - as: 'category', - duplicating: false, - ...whereCategory, + where: process.env.API_ENVIRONMENT === 'production' + ? { isLive: true } + : {}, }, ], where, group: [ - '"LearnAndEarnLevelModel".id', - '"LearnAndEarnLevelModel".prismicId', - '"LearnAndEarnLevelModel"."totalReward"', - 'category."prismicId', - 'category.id', - '"userLevel".status', + '"LearnAndEarnPrismicLevelModel".id', + '"LearnAndEarnPrismicLevelModel".prismicId', + ...userQuery.group, ], - order: [literal('"category".id')], limit, offset, raw: true, })) as unknown as { - id: number; prismicId: string; - totalReward: number; status: string; totalLessons: number; - category?: { - prismicId: string; - }; }[]; - const count = await models.learnAndEarnLevel.count({ + const count = await models.learnAndEarnPrismicLevel.count({ attributes: [], include: [ - { - attributes: [], - model: models.learnAndEarnUserLevel, - as: 'userLevel', - required: false, - where: { - userId, - }, - }, - { - attributes: [], - model: models.learnAndEarnCategory, - as: 'category', - ...whereCategory, - }, + ...userQuery.include, ], where, }); @@ -140,26 +126,20 @@ export async function listLevels( count, rows: userLevels.map( ({ - id, prismicId, - totalReward, totalLessons, status, - category, }) => formatObjectToNumber({ - id, prismicId, - totalReward, totalLessons, status: status || 'available', - category: category?.prismicId, }) ), }; } catch (error) { console.log({ error }); - throw new BaseError('LIST_LEVELS_FAILED', 'list levels failed'); + throw new BaseError(error.name || 'LIST_LEVELS_FAILED', error.message || 'list levels failed'); } } @@ -178,66 +158,95 @@ export async function listLevelsByAdmin(userId: number) { } } -export async function listLessons(userId: number, levelId: number) { +export async function listLessons(levelId: number | string, userId?: number, language?: string) { try { - const user = await models.appUser.findOne({ - attributes: ['language'], - where: { id: userId }, - }); - const lessons = await models.learnAndEarnLesson.findAll({ - include: [ - { - attributes: [ - 'status', - 'points', - 'attempts', - 'completionDate', - ], - model: models.learnAndEarnUserLesson, - as: 'userLesson', - required: false, - where: { - userId, - }, + const include: Includeable[] = []; + + if (userId) { + const user = await models.appUser.findOne({ + attributes: ['language'], + where: { id: userId }, + }); + + if (user?.language) { + language = user.language; + } + + include.push({ + attributes: [ + 'status', + 'points', + 'attempts', + 'completionDate', + ], + model: models.learnAndEarnUserLesson, + as: 'userLesson', + required: false, + where: { + userId, }, - ], + }) + } + + if (!language) { + throw new BaseError('LANGUAGE_NOT_FOUND', 'language not found'); + } + + if (typeof levelId === 'string') { + const level = await models.learnAndEarnPrismicLevel.findOne({ + attributes: ['levelId'], + where: { + prismicId: levelId, + }, + }); + + if (!level) { + throw new BaseError('LEVEL_NOT_FOUND', 'level not found'); + } + levelId = level.levelId; + } + + const lessons = await models.learnAndEarnPrismicLesson.findAll({ + include, where: { levelId, - languages: { [Op.contains]: [user!.language] }, - active: true, + language, ...(process.env.API_ENVIRONMENT === 'production' ? { isLive: true } : {}), }, }); - let totalPoints = 0; - const daysAgo = new Date(); - daysAgo.setDate(daysAgo.getDate() - config.intervalBetweenLessons); - const completedToday = await models.learnAndEarnUserLesson.count({ - where: { - completionDate: { - [Op.gte]: daysAgo.setHours(0, 0, 0, 0), + let totalPoints = 0, + completedToday = 0; + + if (userId) { + const daysAgo = new Date(); + daysAgo.setDate(daysAgo.getDate() - config.intervalBetweenLessons); + completedToday = await models.learnAndEarnUserLesson.count({ + where: { + completionDate: { + [Op.gte]: daysAgo.setHours(0, 0, 0, 0), + }, + userId, }, - userId, - }, - }); + }); + } const mappedLessons = lessons.map( - ({ id, prismicId, levelId, userLesson }: LearnAndEarnLesson) => { + ({ prismicId, levelId, userLesson }: LearnAndEarnPrismicLesson) => { const { status, points, attempts, completionDate } = - userLesson!.length > 0 + !!userLesson && userLesson.length > 0 ? userLesson![0].toJSON() : { - status: 'available', - points: 0, - attempts: 0, - completionDate: null, - }; + status: 'available', + points: 0, + attempts: 0, + completionDate: null, + }; totalPoints += points || 0; return { - id, prismicId, levelId, status, @@ -247,7 +256,6 @@ export async function listLessons(userId: number, levelId: number) { }; } ); - const rewardAvailable = await getRewardAvailable(levelId); return { @@ -257,7 +265,7 @@ export async function listLessons(userId: number, levelId: number) { lessons: mappedLessons, }; } catch (error) { - throw new BaseError('LIST_LESSONS_FAILED', 'list lessons failed'); + throw new BaseError(error.name || 'LIST_LESSONS_FAILED', error.message || 'list lessons failed'); } } diff --git a/packages/core/src/services/learnAndEarn/start.ts b/packages/core/src/services/learnAndEarn/start.ts index 1bc188930..7956cdfed 100644 --- a/packages/core/src/services/learnAndEarn/start.ts +++ b/packages/core/src/services/learnAndEarn/start.ts @@ -1,30 +1,26 @@ import { models } from '../../database'; import { BaseError } from '../../utils/baseError'; -export async function startLesson(userId: number, lessonId: number) { +export async function startLesson(userId: number, prismicId: string) { try { const status = 'started'; - const lesson = await models.learnAndEarnLesson.findOne({ - attributes: ['levelId'], + const lesson = await models.learnAndEarnPrismicLesson.findOne({ + attributes: ['levelId', 'lessonId'], where: { - id: lessonId, - }, - }); - const level = await models.learnAndEarnLevel.findOne({ - attributes: ['id', 'categoryId'], - where: { - id: lesson!.levelId, + prismicId: prismicId, }, }); // create userLesson const userLesson = await models.learnAndEarnUserLesson.findOrCreate({ where: { - lessonId, + lessonId: lesson!.lessonId, + levelId: lesson!.levelId, userId, }, defaults: { - lessonId, + lessonId: lesson!.lessonId, + levelId: lesson!.levelId, userId, points: 0, attempts: 0, @@ -44,24 +40,9 @@ export async function startLesson(userId: number, lessonId: number) { }, }); - const userCategory = await models.learnAndEarnUserCategory.findOrCreate( - { - where: { - categoryId: level!.categoryId, - userId, - }, - defaults: { - categoryId: level!.categoryId, - userId, - status, - }, - } - ); - return { lesson: userLesson[0].toJSON(), level: userLevel[0].toJSON(), - category: userCategory[0].toJSON(), }; } catch (error) { throw new BaseError( diff --git a/packages/core/src/services/learnAndEarn/syncRemote.ts b/packages/core/src/services/learnAndEarn/syncRemote.ts index 3240d1453..fb48a761a 100644 --- a/packages/core/src/services/learnAndEarn/syncRemote.ts +++ b/packages/core/src/services/learnAndEarn/syncRemote.ts @@ -32,6 +32,11 @@ async function getPrismicLearnAndEarn() { truncate: true, transaction: t, }); + // clean lessons + await models.learnAndEarnPrismicLesson.destroy({ + truncate: true, + transaction: t, + }); // insert new levels for ( @@ -42,6 +47,18 @@ async function getPrismicLearnAndEarn() { const prismicLevel = response[levelIndex]; if (prismicLevel.data.id) { + // check if level exists locally + const level = await models.learnAndEarnLevel.findOne({ + attributes: ['id'], + where: { + id: prismicLevel.data.id, + }, + }); + + if (!level?.id) { + continue; + } + const lang = prismicLevel.lang ? prismicLevel.lang.split('-')[0] : 'en'; await models.learnAndEarnPrismicLevel.create({ prismicId: prismicLevel.id, @@ -52,12 +69,6 @@ async function getPrismicLearnAndEarn() { transaction: t, }); - // clean lessons - await models.learnAndEarnPrismicLesson.destroy({ - truncate: true, - transaction: t, - }); - const lessons = prismicLevel.data.lessons; for ( diff --git a/packages/core/src/services/learnAndEarn/userData.ts b/packages/core/src/services/learnAndEarn/userData.ts index 23d25e1de..c55f0daa1 100644 --- a/packages/core/src/services/learnAndEarn/userData.ts +++ b/packages/core/src/services/learnAndEarn/userData.ts @@ -25,7 +25,7 @@ export async function total(userId: number): Promise<{ attributes: ['language'], where: { id: userId }, }); - const levels = (await models.learnAndEarnLevel.findAll({ + const levels = (await models.learnAndEarnPrismicLevel.findAll({ attributes: [ [ literal( @@ -47,8 +47,7 @@ export async function total(userId: number): Promise<{ }, ], where: { - active: true, - languages: { [Op.contains]: [user!.language] }, + language: user!.language, ...(process.env.API_ENVIRONMENT === 'production' ? { isLive: true } : {}) @@ -60,7 +59,7 @@ export async function total(userId: number): Promise<{ }[]; // get lessons - const lessons = (await models.learnAndEarnLesson.findAll({ + const lessons = (await models.learnAndEarnPrismicLesson.findAll({ attributes: [ [ literal( @@ -81,9 +80,14 @@ export async function total(userId: number): Promise<{ required: false, }, ], + group: [ + '"LearnAndEarnPrismicLessonModel".levelId', + '"LearnAndEarnPrismicLessonModel".lessonId', + '"LearnAndEarnPrismicLessonModel".language', + '"userLesson".status' + ], where: { - active: true, - languages: { [Op.contains]: [user!.language] }, + language: user!.language, ...(process.env.API_ENVIRONMENT === 'production' ? { isLive: true } : {}) From d7fa8b90b784df51ba6797e6bc88d70a12c58758 Mon Sep 17 00:00:00 2001 From: Joao Pedro da Silva Date: Wed, 3 May 2023 15:42:36 -0300 Subject: [PATCH 2/5] fix list levels --- packages/api/src/controllers/v2/learnAndEarn.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/api/src/controllers/v2/learnAndEarn.ts b/packages/api/src/controllers/v2/learnAndEarn.ts index 963f80211..ccdbccb12 100644 --- a/packages/api/src/controllers/v2/learnAndEarn.ts +++ b/packages/api/src/controllers/v2/learnAndEarn.ts @@ -47,7 +47,6 @@ class LearnAndEarnController { parseInt(limit, 10), req.user?.userId, status, - category, level, language, ) From b3d5decc828ed6fed8775b9c64cb30ebce803a70 Mon Sep 17 00:00:00 2001 From: Joao Pedro da Silva Date: Wed, 3 May 2023 16:49:11 -0300 Subject: [PATCH 3/5] fix eslint --- packages/core/src/services/learnAndEarn/start.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/services/learnAndEarn/start.ts b/packages/core/src/services/learnAndEarn/start.ts index 7956cdfed..50b1d7c33 100644 --- a/packages/core/src/services/learnAndEarn/start.ts +++ b/packages/core/src/services/learnAndEarn/start.ts @@ -7,7 +7,7 @@ export async function startLesson(userId: number, prismicId: string) { const lesson = await models.learnAndEarnPrismicLesson.findOne({ attributes: ['levelId', 'lessonId'], where: { - prismicId: prismicId, + prismicId, }, }); From 838459a443cc0413695430b6599f0316aad6ae3f Mon Sep 17 00:00:00 2001 From: Joao Pedro da Silva Date: Wed, 3 May 2023 17:00:05 -0300 Subject: [PATCH 4/5] update migration to run on test env --- .../z1661374519-create-learnAndEarnUserLesson.js | 9 +++++++++ .../z1682361632-update-learnAndEarnUserLesson.js | 4 ++++ .../z1682521792-update-learnAndEarnUserLesson.js | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js index 02655dedd..b7a168d9b 100644 --- a/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js +++ b/packages/core/src/database/migrations/z1661374519-create-learnAndEarnUserLesson.js @@ -23,6 +23,15 @@ module.exports = { type: Sequelize.INTEGER, allowNull: false, }, + levelId: { + type: Sequelize.INTEGER, + references: { + model: 'learn_and_earn_level', + key: 'id', + }, + onDelete: 'CASCADE', + allowNull: true, + }, status: { type: Sequelize.ENUM('available', 'started', 'completed'), allowNull: false, diff --git a/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js index f7f18d031..ffc251022 100644 --- a/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js +++ b/packages/core/src/database/migrations/z1682361632-update-learnAndEarnUserLesson.js @@ -3,6 +3,10 @@ // eslint-disable-next-line no-undef module.exports = { async up(queryInterface, Sequelize) { + if (process.env.NODE_ENV === 'test') { + return; + } + // add new columns await queryInterface.addColumn('learn_and_earn_user_lesson', 'levelId', { type: Sequelize.INTEGER, diff --git a/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js b/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js index 6ec111af7..a3f6689d8 100644 --- a/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js +++ b/packages/core/src/database/migrations/z1682521792-update-learnAndEarnUserLesson.js @@ -10,6 +10,10 @@ const client = prismic.createClient(endpoint, { accessToken }); // eslint-disable-next-line no-undef module.exports = { async up(queryInterface, Sequelize) { + if (process.env.NODE_ENV === 'test') { + return; + } + // get all user lesson const userLessons = await queryInterface.sequelize.query( `select learn_and_earn_user_lesson.id as id, learn_and_earn_lesson."prismicId" as "prismicId", learn_and_earn_lesson."levelId" as "levelId" From 4a9b7fab976d17c66a0b02d2673a7754051fca8f Mon Sep 17 00:00:00 2001 From: Joao Pedro da Silva Date: Mon, 8 May 2023 12:53:13 -0300 Subject: [PATCH 5/5] Update packages/api/src/controllers/v2/learnAndEarn.ts Co-authored-by: Bernardo Vieira --- packages/api/src/controllers/v2/learnAndEarn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/controllers/v2/learnAndEarn.ts b/packages/api/src/controllers/v2/learnAndEarn.ts index ccdbccb12..0a698ec64 100644 --- a/packages/api/src/controllers/v2/learnAndEarn.ts +++ b/packages/api/src/controllers/v2/learnAndEarn.ts @@ -99,7 +99,7 @@ class LearnAndEarnController { const id = parseInt(req.params.id, 10); services.learnAndEarn - .listLessons(id ? id : req.params.id, req.user?.userId, language as any) + .listLessons(id ? id : req.params.id, req.user?.userId, language as string) .then((r) => standardResponse(res, 200, true, r)) .catch((e) => standardResponse(res, 400, false, '', { error: e })); };