From bd0679c04242b61f7bdacbc4cfc521953c8776ce Mon Sep 17 00:00:00 2001 From: YoanRos Date: Thu, 12 Sep 2024 10:31:57 +0200 Subject: [PATCH] feat: add back --- .../20240906143112_no_matomoid/migration.sql | 11 ++ api/prisma/schema.prisma | 1 - api/src/controllers/consommation.js | 137 ++------------ api/src/controllers/defis.js | 23 +-- api/src/controllers/mail.js | 12 +- api/src/controllers/reminder.js | 36 ++-- api/src/controllers/strategies.js | 13 +- api/src/controllers/test.js | 52 ++---- api/src/controllers/user.js | 162 ++++++++++++----- api/src/index.js | 4 +- api/src/middlewares/tokenAuth.js | 25 +-- api/src/services/push-notifications.js | 4 +- expo/src/scenes/Auth/Signin.js | 127 +++++++------ expo/src/scenes/Auth/Signup.js | 44 +++-- expo/src/scenes/Infos/AccountInfo.js | 6 +- expo/src/scenes/Infos/ChangeAccountModal.js | 169 ++++++++++++------ expo/src/scenes/Infos/ChangePassword.js | 70 ++++++-- expo/src/scenes/Infos/Transfer.js | 125 ------------- .../WelcomeScreen/EmailConfirmationScreen.js | 4 +- expo/src/services/api.js | 6 + 20 files changed, 484 insertions(+), 547 deletions(-) create mode 100644 api/prisma/migrations/20240906143112_no_matomoid/migration.sql diff --git a/api/prisma/migrations/20240906143112_no_matomoid/migration.sql b/api/prisma/migrations/20240906143112_no_matomoid/migration.sql new file mode 100644 index 000000000..d13af5472 --- /dev/null +++ b/api/prisma/migrations/20240906143112_no_matomoid/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `matomo_id` on the `User` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "User_matomo_id_key"; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "matomo_id"; diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 1f9aaea70..5e08ed623 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -13,7 +13,6 @@ datasource db { model User { id String @id @default(uuid()) - matomo_id String @unique email String @unique password String push_notif_token String @default("") diff --git a/api/src/controllers/consommation.js b/api/src/controllers/consommation.js index f54c4d5f4..eeee26112 100644 --- a/api/src/controllers/consommation.js +++ b/api/src/controllers/consommation.js @@ -6,9 +6,11 @@ const prisma = require("../prisma"); const { getBadgeCatalog } = require("../utils/badges"); const { syncGoalsWithConsos, syncAllGoalsWithConsos } = require("../utils/goals"); const { checksConsecutiveDays, syncDrinkBadgesWithConsos } = require("../utils/drinks"); +const { authenticateToken } = require("../middlewares/tokenAuth"); router.post( "/init", + authenticateToken, // Add authentication middleware catchErrors(async (req, res) => { // kept for retrocompatilibity return res.status(200).send({ ok: true }); @@ -17,32 +19,17 @@ router.post( router.post( "/sync", + authenticateToken, // Add authentication middleware catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - console.log("syncing consos", matomoId); const { drinks, drinksCatalog } = req.body; + const user = req.user; if (!drinks.length) { - await syncDrinkBadgesWithConsos(matomoId); - - await syncAllGoalsWithConsos(matomoId, true); - - // TODO: uncomment this line when the notifications for goals sync is sent - // await syncBadgesWithGoals(matomoId, true); + await syncDrinkBadgesWithConsos(user.id); + await syncAllGoalsWithConsos(user.id, true); return res.status(200).json({ ok: true }); } - const user = await prisma.user.upsert({ - where: { matomo_id: matomoId }, - create: { - matomo_id: matomoId, - email: "yoan.roszak@selego.co", - password: "password12@Abc", - }, - update: {}, - }); - for (const drink of drinks) { if (drink.drinkKey === "no-conso") { const drinkToSave = { @@ -83,12 +70,8 @@ router.post( }); } - await syncDrinkBadgesWithConsos(matomoId); - - await syncAllGoalsWithConsos(matomoId, true); - - // TODO: uncomment this line when the notifications for goals sync is sent - // await syncBadgesWithGoals(matomoId, true); + await syncDrinkBadgesWithConsos(user.id); + await syncAllGoalsWithConsos(user.id, true); return res.status(200).send({ ok: true }); }) @@ -96,16 +79,12 @@ router.post( router.post( "/", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - - /* 1. save conso in DB */ - - let user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); + let user = req.user; const date = req.body.date; - const conso_id = req.body.id; // setup in frontend + const conso_id = req.body.id; const conso = { name: req.body.name, drinkKey: req.body.drinkKey, @@ -129,14 +108,9 @@ router.post( update: conso, create: { ...conso, id: conso_id }, }); - console.log("conso upserted", consoDB.id); } - /* 2. SIDE EFFECTS */ - - // note: the `date` can be ANY date, not just today, - // because the user can update a conso from any date - await syncGoalsWithConsos(matomoId, date); + await syncGoalsWithConsos(user.id, date); const drinksBadgeToShow = await syncDrinkBadgesWithConsos(matomoId); @@ -159,12 +133,10 @@ router.post( router.post( "/fix-missing-key", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); const drinkKey = req.body.drinkKey; const conso_id = req.body.id; - // find user with matomoId await prisma.consommation.update({ where: { id: conso_id }, @@ -178,10 +150,9 @@ router.post( router.delete( "/", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - const user = await prisma.user.findFirst({ where: { matomo_id: matomoId } }); + const user = req.user; const conso_id = req.body.id; const consommation = await prisma.consommation.findFirst({ where: { id: conso_id }, @@ -196,75 +167,11 @@ router.delete( }) ); -const NPSInAppMessage = { - id: "@NPSDone", - title: "5 sec pour nous aider à améliorer l'application\u00A0?", - content: `Nous construisons l'application ensemble et __votre avis sera pris en compte dans les prochaines mises à jour.__ Merci d'avance\u00A0!`, - CTATitle: "Je donne mon avis sur Oz", - CTANavigation: ["NPS_SCREEN", { triggeredFrom: "5 seconds for NPS" }], -}; - -const checkNPSAvailability = async (user) => { - const npsDone = await prisma.appMilestone.findUnique({ where: { id: `${user.id}_@NPSDone` } }); - if (npsDone) return null; - const npsAsked3 = await prisma.appMilestone.findUnique({ where: { id: `${user.id}_@NPSAsked3` } }); - if (npsAsked3) return null; - const allConsos = await prisma.consommation.findMany({ - where: { - userId: user.id, - }, - orderBy: { date: "desc" }, - }); - const enoughConsecutiveDays = checksConsecutiveDays(4, allConsos); - if (!enoughConsecutiveDays) return null; - - const npsAsked2 = await prisma.appMilestone.findUnique({ where: { id: `${user.id}_@NPSAsked2` } }); - - const now = dayjs(); - - if (npsAsked2) { - if (dayjs(npsAsked2.date).diff(now, "day") < 7) { - return null; - } - await prisma.appMilestone.create({ - data: { - id: `${user.id}_@NPSAsked3`, - date: now.format("YYYY-MM-DD"), - userId: user.id, - }, - }); - return NPSInAppMessage; - } - - const npsAsked1 = await prisma.appMilestone.findUnique({ where: { id: `${user.id}_@NPSAsked1` } }); - if (npsAsked1) { - if (dayjs(npsAsked1.date).diff(now, "day") < 7) { - return null; - } - await prisma.appMilestone.create({ - data: { - id: `${user.id}_@NPSAsked2`, - date: now.format("YYYY-MM-DD"), - userId: user.id, - }, - }); - return NPSInAppMessage; - } - await prisma.appMilestone.create({ - data: { - id: `${user.id}_@NPSAsked1`, - date: now.format("YYYY-MM-DD"), - userId: user.id, - }, - }); - return NPSInAppMessage; -}; - router.post( "/update-own-conso", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); + const user = req.user; const oldDrinkKey = req.body?.oldDrinkKey; const drinkKey = req.body?.drinkKey; const doses = req.body?.doses; @@ -272,9 +179,6 @@ router.post( const price = req.body?.price; const volume = req.body?.volume; - // find user with matomoId - let user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); - await prisma.consommation.updateMany({ where: { userId: user.id, drinkKey: oldDrinkKey }, data: { @@ -292,12 +196,9 @@ router.post( router.get( "/get-all-consos", + authenticateToken, // Add authentication middleware catchErrors(async (req, res) => { - const { matomoId } = req.query; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - - // find user with matomoId - let user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); + const user = req.user; const consos = await prisma.consommation.findMany({ where: { userId: user.id }, diff --git a/api/src/controllers/defis.js b/api/src/controllers/defis.js index c26f8dde6..6b60461e8 100644 --- a/api/src/controllers/defis.js +++ b/api/src/controllers/defis.js @@ -3,9 +3,11 @@ const { catchErrors } = require("../middlewares/errors"); const router = express.Router(); const prisma = require("../prisma"); const { getBadgeCatalog, grabBadgeFromCatalog } = require("../utils/badges"); +const { authenticateToken } = require("../middlewares/tokenAuth"); router.post( "/init", + authenticateToken, catchErrors(async (req, res) => { return res.status(200).send({ ok: true }); }) @@ -13,22 +15,11 @@ router.post( router.post( "/", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; + const user = req.user; const completedDays = Number(req.body?.daysValidated); const autoEvaluationDone = req.body?.autoEvaluationDone; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - - const user = await prisma.user.upsert({ - where: { matomo_id: matomoId }, - create: { - matomo_id: matomoId, - email: "yoan.roszak@selego.co", - password: "password12@Abc", - created_from: "Defis", - }, - update: {}, - }); const defis_badges = await prisma.badge.findMany({ where: { userId: user.id, category: "defis" }, @@ -102,11 +93,9 @@ router.post( router.post( "/display", + authenticateToken, catchErrors(async (req, res) => { - const { matomoId } = req.body || {}; - const user = await prisma.user.findUnique({ - where: { matomo_id: matomoId }, - }); + const user = req.user; const badge_defis_to_show = await prisma.badge.findFirst({ where: { userId: user.id, category: "defis", shown: false }, }); diff --git a/api/src/controllers/mail.js b/api/src/controllers/mail.js index e7cf9d8ee..f720abab9 100644 --- a/api/src/controllers/mail.js +++ b/api/src/controllers/mail.js @@ -5,14 +5,15 @@ const { catchErrors } = require("../middlewares/errors"); const router = express.Router(); const { capture } = require("../third-parties/sentry"); const prisma = require("../prisma"); +const { authenticateToken } = require("../middlewares/tokenAuth"); router.post( "/", + authenticateToken, catchErrors(async (req, res) => { - let { matomoId, to, replyTo, replyToName, subject, text, html, attachments } = req.body || {}; - if (!matomoId && req.headers.appversion > 225) { - return res.status(200).json({ ok: true }); - } + let { to, replyTo, replyToName, subject, text, html, attachments } = req.body || {}; + const user = req.user; + if (!subject || (!text && !html)) return res.status(400).json({ ok: false, error: "wrong parameters" }); if (subject === "Context suggestion") { @@ -23,15 +24,12 @@ router.post( .split("\n") .filter(Boolean) .map((line) => line.split(":")); - const matomoId = textAndValues[0][1].trim(); const requestContext = textAndValues[4][1].trim().toLowerCase(); const requestCategory = textAndValues[4][0].split(" ").at(-1).replace(":", "").trim(); - const user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); if (!user) return res.status(400).json({ ok: false, error: "no matomo id" }); await prisma.drinksContextRequest.create({ data: { userId: user.id, - matomo_id: matomoId, context: requestContext, category: requestCategory, }, diff --git a/api/src/controllers/reminder.js b/api/src/controllers/reminder.js index 8bdad9661..803aa22ae 100644 --- a/api/src/controllers/reminder.js +++ b/api/src/controllers/reminder.js @@ -9,6 +9,7 @@ const router = express.Router(); const dayjs = require("dayjs"); const utc = require("dayjs/plugin/utc"); dayjs.extend(utc); +const { authenticateToken } = require("../middlewares/tokenAuth"); const DAYS_OF_WEEK = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]; @@ -58,51 +59,42 @@ const toUtcData = ({ timeHours, timeMinutes, daysOfWeek, timezone }) => { router.put( ["/", "/sync"], + authenticateToken, catchErrors(async (req, res) => { - const { matomoId, pushNotifToken, type, timeHours, timeMinutes, id, daysOfWeek, timezone, disabled } = req.body || {}; + const { pushNotifToken, type, timeHours, timeMinutes, id, daysOfWeek, timezone, disabled } = req.body || {}; + const user = req.user; if (!pushNotifToken) { - capture("reminder api: no push token", { extra: req.body, user: { matomoId } }); + capture("reminder api: no push token", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "no push token" }); } - if (!matomoId) { - capture("reminder api: no matomo id", { extra: req.body, user: { matomoId } }); - return res.status(400).json({ ok: false, error: "no matomo id" }); - } if (type !== "Daily" && type !== "Weekdays") { - capture("reminder api: wrong type", { extra: req.body, user: { matomoId } }); + capture("reminder api: wrong type", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "wrong type" }); } if (!timezone) { - capture("reminder api: wrong timezone", { extra: req.body, user: { matomoId } }); + capture("reminder api: wrong timezone", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "wrong timezone" }); } if (isNaN(timeHours)) { - capture("reminder api: wrong timeHours", { extra: req.body, user: { matomoId } }); + capture("reminder api: wrong timeHours", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "wrong timeHours" }); } if (isNaN(timeMinutes)) { - capture("reminder api: wrong timeMinutes", { extra: req.body, user: { matomoId } }); + capture("reminder api: wrong timeMinutes", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "wrong timeMinutes" }); } if (type === "Weekdays" && !daysOfWeek) { - capture("reminder api: wrong daysOfWeek", { extra: req.body, user: { matomoId } }); + capture("reminder api: wrong daysOfWeek", { extra: req.body, user: user.id }); return res.status(400).json({ ok: false, error: "wrong daysOfWeek" }); } const { utcTimeHours, utcTimeMinutes, utcDaysOfWeek } = toUtcData({ timeHours, timeMinutes, daysOfWeek, timezone }); - const user = await prisma.user.upsert({ - where: { matomo_id: matomoId }, - update: { - push_notif_token: pushNotifToken, - }, - create: { - email: "yoan.roszak@selego.co", - password: "password12@Abc", + await prisma.user.update({ + where: { id: user.id }, + data: { push_notif_token: pushNotifToken, - matomo_id: matomoId, - created_from: "Reminder", }, }); @@ -182,7 +174,6 @@ const reminderCronJob = async (req, res) => { sendPushNotification({ userId: reminder.user.id, - matomoId: reminder.user.matomo_id, pushNotifToken: reminder.user.push_notif_token, channelId: "unique_reminder", ...REMINDER_DATA, @@ -224,7 +215,6 @@ const reminderCronJob = async (req, res) => { sendPushNotification({ userId: reminder.user.id, - matomoId: reminder.user.matomo_id, pushNotifToken: reminder.user.push_notif_token, channelId: "unique_reminder", ...REMINDER_DATA, diff --git a/api/src/controllers/strategies.js b/api/src/controllers/strategies.js index 2979d2ac2..af6fc127b 100644 --- a/api/src/controllers/strategies.js +++ b/api/src/controllers/strategies.js @@ -3,14 +3,13 @@ const express = require("express"); const { catchErrors } = require("../middlewares/errors"); const router = express.Router(); const prisma = require("../prisma"); +const { authenticateToken } = require("../middlewares/tokenAuth"); router.get( "/list", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.query?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - // find user with matomoId - let user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); + const user = req.user; let strategies = await prisma.strategy.findMany({ where: { userId: user.id } }); @@ -20,10 +19,8 @@ router.get( router.post( "/", + authenticateToken, catchErrors(async (req, res) => { - const matomoId = req.body?.matomoId; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - const strategyIndex = req.body.strategyIndex; const feelings = req.body.feelings; const trigger = req.body.trigger; @@ -32,7 +29,7 @@ router.post( const createdAt = req.body.createdAt; const updatedAt = req.body.updatedAt; // find user with matomoId - let user = await prisma.user.findUnique({ where: { matomo_id: matomoId } }); + const user = req.user; await prisma.strategy.upsert({ where: { id: `${user.id}_${strategyIndex}` }, diff --git a/api/src/controllers/test.js b/api/src/controllers/test.js index 1054e4d93..9c42b4830 100644 --- a/api/src/controllers/test.js +++ b/api/src/controllers/test.js @@ -6,9 +6,11 @@ const dayjs = require("dayjs"); const { superUser90DaysInAppModal, superUser30DaysInAppModal, cravingInAppModal } = require("../utils/inAppModals"); const { scheduleNotificationPlan } = require("../utils/notifications"); const { sendPushNotification } = require("../services/push-notifications"); +const { authenticateToken } = require("../middlewares/tokenAuth"); router.get( "/", + authenticateToken, catchErrors(async (req, res) => { // test prisma db connection const conso = await prisma.consommation.findFirst({ @@ -25,6 +27,7 @@ router.get( router.get( "/in-app-modal", + authenticateToken, catchErrors(async (req, res) => { console.log("init"); const modale = req.query?.modale; @@ -53,33 +56,17 @@ router.get( router.post( "/test-notif", + authenticateToken, catchErrors(async (req, res) => { - const { matomoId, type, date } = req.body || {}; - if (!matomoId) { - return res.status(400).json({ ok: false, error: "no matomo id" }); - } + const { type, date } = req.body || {}; + const user = req.user; + try { // Assuming userId should be retrieved from the 'users' variable - let user = await prisma.user.upsert({ - where: { matomo_id: matomoId }, - create: { - email: "yoan.roszak@selego.co", - password: "password12@Abc", - matomo_id: matomoId, - created_from: "test", - }, - update: {}, - }); - - if (!user) { - return res.status(404).json({ ok: false, error: "user not found" }); - } - - const userId = user.id; const notif = await prisma.notification.create({ data: { - user: { connect: { id: userId } }, + user: { connect: { id: user.id } }, type: type ? type : "DEFI1_DAY1", date: date ? dayjs(date).toDate() : dayjs().toDate(), }, @@ -95,17 +82,9 @@ router.post( router.get( "/test-notif", + authenticateToken, catchErrors(async (req, res) => { - const { userId } = req.query || {}; - if (!userId) { - return res.status(400).json({ ok: false, error: "no user id" }); - } - - const user = await prisma.user.findFirst({ - where: { - id: userId, - }, - }); + const user = req.user; if (!user) { console.error(`User with id ${userId} not found`); @@ -113,8 +92,7 @@ router.get( } const results = await sendPushNotification({ - userId, - matomoId: user.matomo_id, + userId: user.id, pushNotifToken: user.push_notif_token, title: "La notif elle est là", body: "This is a test notification", @@ -129,12 +107,10 @@ router.get( router.post( "/launch-notification-plan", + authenticateToken, catchErrors(async (req, res) => { - const { matomoId } = req.body || {}; - if (!matomoId) { - return res.status(400).json({ ok: false, error: "no matomo id" }); - } - scheduleNotificationPlan(matomoId); + const user = req.user; + scheduleNotificationPlan(user.id); return res.status(200).json({ ok: true }); }) ); diff --git a/api/src/controllers/user.js b/api/src/controllers/user.js index b3410b967..3d5b7f50a 100644 --- a/api/src/controllers/user.js +++ b/api/src/controllers/user.js @@ -8,6 +8,7 @@ const validator = require("validator"); const { isStrongPassword } = require("validator"); const jwt = require("jsonwebtoken"); const { JWT_SECRET } = require("../config"); +const { authenticateToken } = require("../middlewares/tokenAuth"); // 1 year const JWT_MAX_AGE = "365d"; @@ -25,9 +26,7 @@ function validatePassword(password) { router.post( "/signup", catchErrors(async (req, res) => { - const { email, password, matomoId } = req.body || {}; - console.log("signup", email, password, matomoId); - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); + const { email, password } = req.body || {}; if (!email || !password) { return res.status(400).json({ ok: false, error: "missing email or password" }); @@ -44,42 +43,32 @@ router.post( const user = await prisma.user.findUnique({ where: { email }, }); - console.log("user", user); if (user) { return res.status(400).json({ ok: false, error: "email already exists" }); } const hashedPassword = await bcrypt.hash(password, 10); - const updateObj = {}; - updateObj.email = email; - updateObj.password = hashedPassword; - - await prisma.user.upsert({ - where: { matomo_id: matomoId }, - update: updateObj, - create: { - matomo_id: matomoId, - ...updateObj, - }, + + const newUser = await prisma.user.create({ + data: { email, password: hashedPassword }, }); const token = jwt.sign({ email }, JWT_SECRET, { expiresIn: JWT_MAX_AGE, }); - return res.status(200).send({ ok: true, token }); + return res.status(200).send({ ok: true, token, newUser }); }) ); router.post( "/signin", catchErrors(async (req, res) => { - const { email, password, matomoId } = req.body || {}; + const { email, password } = req.body || {}; validator.isEmail(email); validator.isStrongPassword(password); - console.log("signin", email, password, matomoId); - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); + console.log("signin", email, password); if (!email || !password) { return res.status(400).json({ ok: false, error: "missing email or password" }); @@ -95,7 +84,7 @@ router.post( console.log("user", user); // const match = await bcrypt.compare(password, user.password); - const match = password === user.password; + const match = await bcrypt.compare(password, user.password); if (!match) { return res.status(400).json({ ok: false, error: "wrong email or password" }); @@ -105,7 +94,7 @@ router.post( expiresIn: JWT_MAX_AGE, }); - return res.status(200).send({ ok: true, token }); + return res.status(200).send({ ok: true, token, user }); }) ); @@ -126,20 +115,18 @@ router.get( return res.status(400).json({ ok: false, error: "user not found" }); } - return res.status(200).send({ ok: true, user, token }); + return res.status(200).send({ ok: true, user, token, user }); }) ); router.put( "/", + authenticateToken, catchErrors(async (req, res) => { - const { matomoId } = req.body || {}; - - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); + const user = req.user; const updateObj = {}; - let created_from = "User"; if (req.body.hasOwnProperty("pushNotifToken")) { updateObj.push_notif_token = req.body.pushNotifToken ?? ""; created_from = "User-PushNotif"; @@ -151,32 +138,125 @@ router.put( // TODO: fix concurrency issue Unique constraint failed on the fields: (`matomo_id`) // using a "version" field ? https://www.prisma.io/docs/guides/performance-and-optimization/prisma-client-transactions-guide#optimistic-concurrency-control - await prisma.user.upsert({ - where: { matomo_id: matomoId }, - update: updateObj, - create: { - matomo_id: matomoId, - email: "yoan.roszak@selego.co", - password: "password12@Abc", - created_from, - ...updateObj, - }, + await prisma.user.update({ + where: { id: user.id }, + data: updateObj, }); return res.status(200).send({ ok: true }); }) ); +router.post( + "/delete", + authenticateToken, + catchErrors(async (req, res) => { + const { email, password } = req.body || {}; + console.log("delete", email, password); + if (!email || !password) { + return res.status(400).json({ ok: false, error: "Missing email or password" }); + } + + // Find the user by email + const user = await prisma.user.findUnique({ + where: { email }, + }); + + if (!user) { + return res.status(400).json({ ok: false, error: "Wrong email or password" }); + } + + // Compare provided password with the hashed password + const match = await bcrypt.compare(password, user.password); + if (!match) { + return res.status(400).json({ ok: false, error: "Wrong email or password" }); + } + + // Delete the user + await prisma.user.delete({ + where: { email }, + }); + + return res.status(200).send({ ok: true, message: "User deleted successfully" }); + }) +); + +router.post( + "/update", + authenticateToken, + catchErrors(async (req, res) => { + const { email, password, newPassword } = req.body || {}; + console.log("update", email, password, newPassword); + + if (!email || !password) { + return res.status(400).json({ ok: false, error: "Missing email or password" }); + } + + if (!validator.isEmail(email)) { + return res.status(400).json({ ok: false, error: "Invalid email" }); + } + + if (!validator.isStrongPassword(password)) { + return res.status(400).json({ ok: false, error: "Password is not strong enough" }); + } + + const token = req.headers.authorization?.split(" ")[1]; + if (!token) { + return res.status(401).json({ ok: false, error: "No token provided" }); + } + + const decoded = jwt.verify(token, JWT_SECRET); + + const user = await prisma.user.findUnique({ + where: { email: decoded.email }, + }); + + if (!user) { + return res.status(400).json({ ok: false, error: "Wrong email or password" }); + } + console.log("fkrfikefk", password, user.password); + const match = await bcrypt.compare(password, user.password); + if (!match) { + return res.status(400).json({ ok: false, error: "Wrong email or password" }); + } + + const updateObj = {}; + console.log("alo oui non", newPassword); + if (newPassword) { + if (!validator.isStrongPassword(newPassword)) { + return res.status(400).json({ ok: false, error: "New password is not strong enough" }); + } + updateObj.password = await bcrypt.hash(newPassword, 10); + } + console.log("breolirdo", email, decoded.email); + let newToken = ""; + if (email && email !== decoded.email) { + updateObj.email = email; + newToken = jwt.sign({ email }, JWT_SECRET, { + expiresIn: JWT_MAX_AGE, + }); + } + + if (Object.keys(updateObj).length === 0) { + return res.status(400).json({ ok: false, error: "Nothing to update" }); + } + console.log("updateObj", updateObj); + + await prisma.user.update({ + where: { email: decoded.email }, + data: updateObj, + }); + + return res.status(200).send({ ok: true, token: newToken ?? "", message: "User updated successfully" }); + }) +); router.get( "/location", + authenticateToken, catchErrors(async (req, res) => { - const { matomoId } = req.query || {}; + const user = req.user; let isWellLocated = false; - if (!matomoId) return res.status(400).json({ ok: false, error: "no matomo id" }); - const user = await prisma.user.findUnique({ - where: { matomo_id: matomoId }, - }); if (!user) return res.status(404).json({ ok: false, error: "user not found" }); const xforwarded = req.headers["x-forwarded-for"]; diff --git a/api/src/index.js b/api/src/index.js index b8c50c3cd..949615b04 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -56,9 +56,9 @@ app.use(helmet()); // sentry context/user app.use(async (req, res, next) => { - const { matomoId } = req.body || {}; + const user = req.user; const { appversion, appdevice, currentroute } = req.headers || {}; - if (matomoId) Sentry.setUser({ id: matomoId }); + if (user) Sentry.setUser({ id: user.id, username: user.username }); if (appversion) Sentry.setTag("appversion", appversion); if (appdevice) Sentry.setTag("appdevice", appdevice); if (currentroute) Sentry.setTag("currentroute", currentroute); diff --git a/api/src/middlewares/tokenAuth.js b/api/src/middlewares/tokenAuth.js index 82a801566..9dfcceee6 100644 --- a/api/src/middlewares/tokenAuth.js +++ b/api/src/middlewares/tokenAuth.js @@ -1,33 +1,34 @@ +const jwt = require("jsonwebtoken"); +const { JWT_SECRET } = require("../config"); +const prisma = require("../prisma"); -import jwt from "jsonwebtoken"; -import { JWT_SECRET } from "../config"; -import { prisma } from "../db"; - - - - -export const authenticateToken = async (req, res, next) => { +const authenticateToken = async (req, res, next) => { const token = req.headers.authorization?.split(" ")[1]; // Bearer token extraction + console.log("token", token); if (!token) { return res.status(401).json({ ok: false, error: "No token provided" }); } try { + console.log("verifying token", token); const decoded = jwt.verify(token, JWT_SECRET); - const user = await prisma.user.findUnique({ + console.log("decoded", decoded); + const user = await prisma.user.findFirst({ where: { email: decoded.email }, }); - + console.log("user"); + console.log("user", user); if (!user) { return res.status(400).json({ ok: false, error: "User not found" }); } + console.log("user found", user); // Attach user info to req object req.user = user; - next(); // Proceed to the next middleware/route handler + next(); } catch (error) { return res.status(403).json({ ok: false, error: "Token is invalid or expired" }); } }; -module.exports = { authenticateToken }; \ No newline at end of file +module.exports = { authenticateToken }; diff --git a/api/src/services/push-notifications.js b/api/src/services/push-notifications.js index 9bb3f1fea..792910c09 100644 --- a/api/src/services/push-notifications.js +++ b/api/src/services/push-notifications.js @@ -89,9 +89,9 @@ const sendPushNotification = async ({ userId, matomoId, pushNotifToken, title, b if (error === "Requested entity was not found") { // https://stackoverflow.com/a/56218146/5225096 - await prisma.user.upsert({ + await prisma.user.update({ where: { matomo_id: matomoId }, - update: { + data: { push_notif_token: null, }, }); diff --git a/expo/src/scenes/Auth/Signin.js b/expo/src/scenes/Auth/Signin.js index 81644ef41..075860315 100644 --- a/expo/src/scenes/Auth/Signin.js +++ b/expo/src/scenes/Auth/Signin.js @@ -1,8 +1,15 @@ import React, { useState } from "react"; import ButtonPrimary from "../../components/ButtonPrimary"; -import { Image, View, TextInput, TouchableOpacity, Text } from "react-native"; -import Wave from "../../components/illustrations/onboarding/Wave"; -import { screenWidth } from "../../styles/theme"; +import { + Image, + View, + TextInput, + TouchableOpacity, + Text, + KeyboardAvoidingView, + Platform, + ScrollView, +} from "react-native"; import API from "../../services/api"; import { storage } from "../../services/storage"; import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; @@ -11,17 +18,18 @@ const SigninScreen = ({ navigation }) => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [hidePassword, setHidePassword] = useState(true); + const signin = async () => { const response = await API.post({ - path: "user/signin", + path: "/user/signin", body: { - matomoId: storage.getString("@UserIdv2"), email: email.toLowerCase(), password, }, }); if (response.ok) { - storage.set("@Token", response.token); + API.setToken(response.token); + storage.set("@User", response.user.email); const onBoardingDone = storage.getBoolean("@OnboardingDoneWithCGU"); if (!onBoardingDone) navigation.push("WELCOME_SWIPER"); @@ -32,64 +40,65 @@ const SigninScreen = ({ navigation }) => { }; return ( - - - - - - - - E-mail - - Mot de passe - + + + + + + + + E-mail - setHidePassword(!hidePassword)}> - {hidePassword ? ( - - ) : ( - - )} + Mot de passe + + + setHidePassword(!hidePassword)}> + {hidePassword ? ( + + ) : ( + + )} + + + navigation.push("FORGOT_PASSWORD")}> + Mot de passe oublié ? + + + + + { + signin(); + }} + /> + navigation.push("WELCOME")}> + Créer un compte - - navigation.push("FORGOT_PASSWORD")}> - Mot de passe oublié ? - - - - - - - - { - signin(); - }} - /> - navigation.push("WELCOME")}> - Créer un compte - - - + + ); }; diff --git a/expo/src/scenes/Auth/Signup.js b/expo/src/scenes/Auth/Signup.js index 7fd42394c..99eea6bd7 100644 --- a/expo/src/scenes/Auth/Signup.js +++ b/expo/src/scenes/Auth/Signup.js @@ -1,5 +1,14 @@ import React, { useEffect, useState } from "react"; -import { View, TextInput, TouchableOpacity, Text, SafeAreaView, KeyboardAvoidingView, Platform } from "react-native"; +import { + View, + TextInput, + TouchableOpacity, + Text, + SafeAreaView, + KeyboardAvoidingView, + Platform, + ScrollView, +} from "react-native"; import API from "../../services/api"; import { storage } from "../../services/storage"; import AntDesign from "@expo/vector-icons/AntDesign"; @@ -48,18 +57,16 @@ const SignupScreen = ({ navigation }) => { }; const signup = async () => { - const matomoId = storage.getString("@UserIdv2"); const response = await API.post({ path: "/user/signup", body: { - matomoId, email, password, }, }); if (response.ok) { - storage.set("@Email", email); - storage.set("@Token", response.token); + storage.set("@User", response.newUser.email); + API.setToken(response.token); navigation.navigate("EMAIL_CONFIRMATION"); } else { alert("Erreur lors de l'inscription"); @@ -79,7 +86,12 @@ const SignupScreen = ({ navigation }) => { return ( - + Créer un compte @@ -160,16 +172,16 @@ const SignupScreen = ({ navigation }) => { )} - - - - Valider - - + + + Valider + + + ); diff --git a/expo/src/scenes/Infos/AccountInfo.js b/expo/src/scenes/Infos/AccountInfo.js index 731b04219..9519f4ade 100644 --- a/expo/src/scenes/Infos/AccountInfo.js +++ b/expo/src/scenes/Infos/AccountInfo.js @@ -4,11 +4,10 @@ import BackButton from "../../components/BackButton"; import { storage } from "../../services/storage"; const AccountInfo = ({ navigation }) => { - const currentEmail = storage.getString("@Email"); + const currentEmail = storage.getString("@User"); const [email, setEmail] = useState(currentEmail); const [isButtonDisabled, setIsButtonDisabled] = useState(true); const [isEmailValid, setIsEmailValid] = useState(false); - storage.set("@Email", "yoan.roszak@selego.co"); const handleEmailChange = (text) => { setEmail(text); @@ -34,8 +33,9 @@ const AccountInfo = ({ navigation }) => { /> { - navigation.navigate("CHANGE_PASSWORD"); + navigation.navigate("CHANGE_ACCOUNT", { email: email }); }} className={`mt-2 rounded-full px-6 py-2 ${isButtonDisabled ? "bg-[#EA6C96]" : "bg-[#de285e]"}`} > diff --git a/expo/src/scenes/Infos/ChangeAccountModal.js b/expo/src/scenes/Infos/ChangeAccountModal.js index 6fd29afe4..a8b78f858 100644 --- a/expo/src/scenes/Infos/ChangeAccountModal.js +++ b/expo/src/scenes/Infos/ChangeAccountModal.js @@ -1,12 +1,14 @@ import React, { useState, useEffect } from "react"; import { Path, Svg } from "react-native-svg"; -import { View, Text, TouchableOpacity, TextInput } from "react-native"; +import { View, Text, TouchableOpacity, TextInput, KeyboardAvoidingView, Platform } from "react-native"; import { hitSlop } from "../../styles/theme"; import { SafeAreaView } from "react-native-safe-area-context"; import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; +import API from "../../services/api"; +import { storage } from "../../services/storage"; const ChangeAccountModal = ({ navigation, route }) => { - const isChangeEmail = route?.params?.from === "changeEmail"; + const email = route?.params?.email; const [password, setPassword] = useState(""); const [hidePassword, setHidePassword] = useState(true); @@ -16,71 +18,122 @@ const ChangeAccountModal = ({ navigation, route }) => { setIsButtonDisabled(password.length === 0); }, [password]); + const modifyEmail = async () => { + API.post({ + path: "/user/update", + body: { + password, + email, + }, + }).then((response) => { + if (response.ok) { + if (response.token) API.setToken(response.token); + storage.set("@User", email).then(() => { + alert("Votre adresse e-mail a bien été modifiée"); + }); + } else { + alert("Erreur lors de la modification de l'adresse e-mail"); + } + }); + }; + + const deleteAccount = async () => { + const user = storage.getString("@User"); + API.post({ + path: "/user/delete", + body: { + password, + email: user, + }, + }).then((response) => { + if (response.ok) { + storage.clearAll(); + API.setToken(null); + navigation.popToTop(); + } else { + alert("Erreur lors de la suppression du compte"); + } + }); + }; + return ( - - - { - navigation.navigate("CRAVING", { screen: "CRAVING_STRATEGIES" }); - }} - > - - - - - - - - - {isChangeEmail ? "Modifier l'e-mail" : "Supprimer mon compte"} - - - {isChangeEmail - ? "Afin de confirmer la modification de l’adresse e-mail du compte, veuillez saisir votre mot de passe ci-dessous." - : "Afin de confirmer la suppression de votre compte, veuillez saisir votre mot de passe ci-dessous."} - - - Mot de passe - - - setHidePassword(!hidePassword)}> - {hidePassword ? ( - - ) : ( - - )} - - - - - + + + { - navigation.goBack(); + navigation.navigate("CRAVING", { screen: "CRAVING_STRATEGIES" }); }} > - - {isChangeEmail ? "Valider" : "Confirmer la suppression"} - + + + + + + + {email ? "Modifier l'e-mail" : "Supprimer mon compte"} + + + {email + ? "Afin de confirmer la modification de l’adresse e-mail du compte, veuillez saisir votre mot de passe ci-dessous." + : "Afin de confirmer la suppression de votre compte, veuillez saisir votre mot de passe ci-dessous."} + + + Mot de passe + + + setHidePassword(!hidePassword)}> + {hidePassword ? ( + + ) : ( + + )} + + + + + + { + if (!email) { + console.log("delete account"); + deleteAccount(); + return; + } + modifyEmail(); + navigation.goBack(); + }} + > + + {email ? "Valider" : "Confirmer la suppression"} + + + + - + ); }; diff --git a/expo/src/scenes/Infos/ChangePassword.js b/expo/src/scenes/Infos/ChangePassword.js index 748d718fc..afa7bc6e0 100644 --- a/expo/src/scenes/Infos/ChangePassword.js +++ b/expo/src/scenes/Infos/ChangePassword.js @@ -1,9 +1,20 @@ import React, { useEffect, useState } from "react"; -import { View, TextInput, TouchableOpacity, Text, SafeAreaView, KeyboardAvoidingView, Platform } from "react-native"; +import { + View, + TextInput, + TouchableOpacity, + Text, + SafeAreaView, + KeyboardAvoidingView, + Platform, + ScrollView, +} from "react-native"; import AntDesign from "@expo/vector-icons/AntDesign"; import Entypo from "@expo/vector-icons/Entypo"; import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; import BackButton from "../../components/BackButton"; +import API from "../../services/api"; +import { storage } from "../../services/storage"; const ChangePassword = ({ navigation }) => { const [formerPassword, setFormerPassword] = useState(""); @@ -41,6 +52,26 @@ const ChangePassword = ({ navigation }) => { setIsMatching(password === text); }; + const updatePassword = async () => { + const email = storage.getString("@User"); + + API.post({ + path: "/user/update", + body: { + email, + password: formerPassword, + newPassword: password, + }, + }).then((response) => { + if (response.ok) { + alert("Votre mot de passe a bien été modifié"); + navigation.goBack(); + } else { + alert("Erreur lors de la modification du mot de passe"); + } + }); + }; + useEffect(() => { if (isStrongPassword && isMatching && isDifferent) { setIsButtonDisabled(false); @@ -53,10 +84,19 @@ const ChangePassword = ({ navigation }) => { return ( - - + + + - Modifier le mot de passe Veuillez saisir votre mot de passe actuel, ainsi que votre nouveau mot de passe. @@ -140,17 +180,17 @@ const ChangePassword = ({ navigation }) => { )} - - - navigation.push("TABS")} - className={`rounded-full px-6 py-3 ${isButtonDisabled ? "bg-[#EA6C96]" : "bg-[#de285e]"}`} - // disabled={isButtonDisabled} - > - Valider - - + + { + updatePassword(); + }} + className={`rounded-full px-6 py-3 ${isButtonDisabled ? "bg-[#EA6C96]" : "bg-[#de285e]"}`} + > + Valider + + + ); diff --git a/expo/src/scenes/Infos/Transfer.js b/expo/src/scenes/Infos/Transfer.js index 0d919b87a..c90c73418 100644 --- a/expo/src/scenes/Infos/Transfer.js +++ b/expo/src/scenes/Infos/Transfer.js @@ -4,12 +4,7 @@ import WrapperContainer from "../../components/WrapperContainer.js"; import { storage } from "../../services/storage.js"; import API from "../../services/api.js"; import * as DocumentPicker from "expo-document-picker"; -import RNFS from "react-native-fs"; -import Share from "react-native-share"; import TipIcon from "../../components/illustrations/TipIcon.js"; -import ClickIcon from "../../components/illustrations/icons/ClickIcon.js"; -import UploadIcon from "../../components/illustrations/icons/UploadIcon.js"; -import FolderIcon from "../../components/illustrations/icons/FolderIcon.js"; import OppositeArrowsIcon from "../../components/illustrations/icons/OppositeArrowsIcon.js"; import CloudIcon from "../../components/illustrations/icons/CloudIcon.js"; import DownloadIcon from "../../components/illustrations/icons/DownloadIcon.js"; @@ -17,61 +12,6 @@ import { logEvent } from "../../services/logEventsWithMatomo.js"; import RNRestart from "react-native-restart"; const Transfer = ({ navigation }) => { - const exportData = async () => { - logEvent({ category: "TRANSFER", action: "EXPORT_DATA" }); - // Storage - const allStorage = storage.getAllKeys(); - const filteredStorage = allStorage.filter( - (key) => !key.startsWith("STORAGE_KEY_PUSH_NOTIFICATION") && key !== "@ExportedData" - ); - const toExportData = {}; - filteredStorage.forEach((key) => { - // On vérifie si la valeur est un objet ou un boolean - // Si c'est un objet, on le parse en JSON - // Si c'est un boolean, on le récupère en boolean - // Sinon, on le récupère en string - const stringValue = storage.getString(key); - const booleanValue = storage.getBoolean(key); - let value; - if (stringValue !== null && stringValue !== undefined) { - try { - value = JSON.parse(stringValue); - } catch (e) { - value = stringValue; - } - } else if (booleanValue !== null && booleanValue !== undefined) { - value = booleanValue; - } else { - value = null; - } - - if (value !== null) toExportData[key] = value; - }); - const jsonExport = JSON.stringify(toExportData); - const path = `${RNFS.DocumentDirectoryPath}/export-oz-ensemble.json`; - - await RNFS.writeFile(path, jsonExport, "utf8"); - - const shareOptions = { - url: `file://${path}`, - title: "Données exportées", - message: "Voici vos données exportées depuis l'application Oz Ensemble", - }; - - try { - await Share.open(shareOptions).then(() => { - (res) => { - console.log(res); - logEvent({ category: "TRANSFER", action: "EXPORT_DATA_SUCCESS" }); - Alert.alert("Vos données ont bien été sauvegardées."); - storage.set("@ExportedData", true); - }; - }); - } catch (error) { - console.log("Error sharing:", error); - Alert.alert("Erreur lors du partage des données."); - } - }; const importData = async () => { logEvent({ category: "TRANSFER", action: "IMPORT_DATA" }); try { @@ -175,72 +115,7 @@ const Transfer = ({ navigation }) => { return ( - - Transférez les données de votre profil de votre ancien vers votre nouveau téléphone (incluant vos consos - déclarées, vos activités, vos badges gagnés, ...) en suivant les 2 étapes suivantes : - - Etape 1 - - Vous êtes sur votre ancien téléphone. - - - Les sous-étapes ci-dessous vont vous permettre de sauvegarder l’ensemble de vos données Oz sous la forme d’un - fichier. - - - - - Veuillez lire l’ensemble des instructions ci-dessous avant de démarrer la sauvegarde. - - - - - - Cliquez sur le bouton “Sauvegarder mes données Oz” ci-dessous, - - - - - - - Une fenêtre s’ouvre, sélectionnez une des méthodes proposées pour sauvegarder vos données Oz. Vous pouvez - réaliser cette sauvegarde : - - - {"\u2022"} - via le cloud, - - - {"\u2022"} - via une application de messagerie, - - - {"\u2022"} - via votre adresse e-mail ... - - - - - - - Sauvegarder mes données Oz - - - - - - PUIS - - - - Etape 2 - - Vous êtes sur votre nouveau téléphone. - Les sous-étapes ci-dessous vont vous permettre de récupérer l’ensemble de vos données Oz. diff --git a/expo/src/scenes/WelcomeScreen/EmailConfirmationScreen.js b/expo/src/scenes/WelcomeScreen/EmailConfirmationScreen.js index 19c240dd1..49620f576 100644 --- a/expo/src/scenes/WelcomeScreen/EmailConfirmationScreen.js +++ b/expo/src/scenes/WelcomeScreen/EmailConfirmationScreen.js @@ -7,7 +7,7 @@ import { storage } from "../../services/storage"; const EmailConfirmationScreen = ({ navigation }) => { const isOldUser = storage.getBoolean("@IsOldUser"); // const isReinitialisingPassword = storage.getBoolean("@IsReinitialisingPassword"); - const isReinitialisingPassword = true; + const isReinitialisingPassword = false; const [code, setCode] = useState(["", "", "", "", "", ""]); const inputRefs = useRef([]); const handleCodeChange = (text, index) => { @@ -45,7 +45,7 @@ const EmailConfirmationScreen = ({ navigation }) => { {code.map((digit, index) => ( - {index === 3 && } {/* Adds extra space after the third box */} + {index === 3 && } { class ApiService { host = API_HOST; scheme = SCHEME; + + setToken = (newToken) => { + this.token = newToken; + }; + getUrl = (path, query) => { return new URI().host(this.host).scheme(this.scheme).path(path).setSearch(query).toString(); }; @@ -37,6 +42,7 @@ class ApiService { appversion: deviceInfoModule.getBuildNumber(), appdevice: Platform.OS, currentroute: this.navigation?.getCurrentRoute?.()?.name, + Authorization: `Bearer ${this.token}`, ...headers, }, body: body ? JSON.stringify(body) : null,