diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index aa016058..a2bcac4e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,36 +2,41 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node-postgres // Update the VARIANT arg in docker-compose.yml to pick a Node.js version { + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.schema.json", "name": "bde-isima", "dockerComposeFile": "docker-compose.yml", "service": "bde_isima", "workspaceFolder": "/workspace", // Set *default* container specific settings.json values on container create. - "settings": { - "typescript.preferences.importModuleSpecifier": "non-relative", - "typescript.tsdk": "node_modules/typescript/lib", - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.formatOnPaste": false, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, - "files.trimTrailingWhitespace": true + "customizations": { + "vscode": { + "settings": { + "typescript.preferences.importModuleSpecifier": "non-relative", + "typescript.tsdk": "node_modules/typescript/lib", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "files.trimTrailingWhitespace": true + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-azuretools.vscode-docker", + "mikestead.dotenv", + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "graphql.vscode-graphql", + "ms-vscode.vscode-typescript-next", + "esbenp.prettier-vscode", + "mgmcdermott.vscode-language-babel", + "prisma.prisma", + "shardulm94.trailing-spaces", + "bradlc.vscode-tailwindcss" + ] + } }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-azuretools.vscode-docker", - "mikestead.dotenv", - "editorconfig.editorconfig", - "dbaeumer.vscode-eslint", - "graphql.vscode-graphql", - "ms-vscode.vscode-typescript-next", - "esbenp.prettier-vscode", - "mgmcdermott.vscode-language-babel", - "prisma.prisma", - "shardulm94.trailing-spaces", - "bradlc.vscode-tailwindcss" - ], // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [ 3000 diff --git a/.env.template b/.env.template index 9b637962..d6e31145 100644 --- a/.env.template +++ b/.env.template @@ -4,12 +4,7 @@ DATABASE_URL=postgresql://postgres:postgres@bde_isima_pg:5432/postgres NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000 NEXT_PUBLIC_GA_TRACKING_ID=REDACTED -NEXT_PUBLIC_DISCORD_SERVER_URL=https://discord.gg/bnJ3narzF3 - -LYF_API_VENDOR_ID=deebb957-9025-4894-b52d-24493cdb7278 -LYF_API_SECRET_KEY=B52DFC6C7F4AA054CDB08E38B2298C97A4A12039 -LYF_CREDIT_CARD_API_URL=https://sandbox-webpos.lyf.eu/fr/plugin/PaymentCb.aspx -LYF_FROM_APPLICATION_API_URL=https://sandbox-webpos.lyf.eu/fr/plugin/Payment.aspx +NEXT_PUBLIC_DISCORD_SERVER_URL=REDACTED SESSION_SECRET_KEY=generate_on_https://randomkeygen.com diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7256f21c..28511bd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,12 +45,9 @@ jobs: envkey_CONTACT_MAIL: ${{ secrets.CONTACT_MAIL }} envkey_DATABASE_URL: ${{ secrets.DATABASE_URL }} envkey_SESSION_SECRET_KEY: ${{ secrets.SESSION_SECRET_KEY }} - envkey_LYF_API_VENDOR_ID: ${{ secrets.LYF_API_VENDOR_ID }} - envkey_LYF_API_SECRET_KEY: ${{ secrets.LYF_API_SECRET_KEY }} - envkey_LYF_FROM_APPLICATION_API_URL: ${{ secrets.LYF_FROM_APPLICATION_API_URL }} - envkey_LYF_CREDIT_CARD_API_URL: ${{ secrets.LYF_CREDIT_CARD_API_URL }} envkey_NEXT_PUBLIC_FRONTEND_URL: ${{ secrets.NEXT_PUBLIC_FRONTEND_URL }} envkey_NEXT_PUBLIC_GA_TRACKING_ID: ${{ secrets.NEXT_PUBLIC_GA_TRACKING_ID }} + envkey_NEXT_PUBLIC_DISCORD_SERVER_URL: ${{ secrets.NEXT_PUBLIC_DISCORD_SERVER_URL }} envkey_SMTP_EMAIL: bde.isima.webmaster@gmail.com envkey_SMTP_USER: bde.isima.webmaster envkey_SMTP_HOST: ${{ secrets.SMTP_HOST }} diff --git a/app/blitz-server.ts b/app/blitz-server.ts index ec61602c..2dfcbedd 100644 --- a/app/blitz-server.ts +++ b/app/blitz-server.ts @@ -2,10 +2,15 @@ import db from 'db'; import { AuthServerPlugin, PrismaStorage, simpleRolesIsAuthorized } from '@blitzjs/auth'; import { setupBlitzServer } from '@blitzjs/next'; +import { BlitzLogger } from 'blitz'; import { authConfig } from 'app/blitz-client'; export const { gSSP, gSP, api } = setupBlitzServer({ + logger: BlitzLogger({ + minLevel: 'warn', + colorizePrettyLogs: true, + }), plugins: [ AuthServerPlugin({ ...authConfig, diff --git a/app/components/dashboard/cashing/CashingDialog.tsx b/app/components/dashboard/cashing/CashingDialog.tsx index 5dbc5589..21886a63 100644 --- a/app/components/dashboard/cashing/CashingDialog.tsx +++ b/app/components/dashboard/cashing/CashingDialog.tsx @@ -30,6 +30,7 @@ import { isTroll } from 'app/core/utils/listeux_or_troll'; import getTransactions from 'app/entities/transactions/queries/getTransactions'; import getUser from 'app/entities/users/queries/getUser'; import getUsers from 'app/entities/users/queries/getUsers'; +import Adherent from 'app/components/hub/transactions/display/Adherent'; const Catalog = lazy(() => import('./catalog/Catalog')); const AdminTransfer = lazy(() => import('./adminTransfer/AdminTransfer')); @@ -99,6 +100,10 @@ export default function CashingDialog({ user, onSelection, onClear }) { + }> + + + }> diff --git a/app/components/dashboard/clubs/ClubForm.tsx b/app/components/dashboard/clubs/ClubForm.tsx index c71cd50f..3ba0a051 100644 --- a/app/components/dashboard/clubs/ClubForm.tsx +++ b/app/components/dashboard/clubs/ClubForm.tsx @@ -2,7 +2,7 @@ import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; import InputAdornment from '@mui/material/InputAdornment'; import { Club } from 'db'; -import { TextField } from 'mui-rff'; +import { Switches, TextField } from 'mui-rff'; import OpenInNew from '@mui/icons-material/OpenInNewTwoTone'; @@ -43,7 +43,8 @@ export default function ClubForm(props: ClubFormProps) { facebookURL: props.initialValues?.facebookURL, twitterURL: props.initialValues?.twitterURL, instagramURL: props.initialValues?.instagramURL, - customURL: props.initialValues?.customURL + customURL: props.initialValues?.customURL, + isPublic: props.initialValues?.isPublic }} onSubmit={onSubmit} autoComplete="off" @@ -84,6 +85,8 @@ export default function ClubForm(props: ClubFormProps) { + + diff --git a/app/components/dashboard/partners/PartnerForm.tsx b/app/components/dashboard/partners/PartnerForm.tsx index cbb6a189..47eac912 100644 --- a/app/components/dashboard/partners/PartnerForm.tsx +++ b/app/components/dashboard/partners/PartnerForm.tsx @@ -1,7 +1,7 @@ import IconButton from '@mui/material/IconButton'; import InputAdornment from '@mui/material/InputAdornment'; import { Partner } from 'db'; -import { TextField } from 'mui-rff'; +import { Switches, TextField } from 'mui-rff'; import OpenInNew from '@mui/icons-material/OpenInNewTwoTone'; @@ -37,7 +37,8 @@ export default function PartnerForm(props: PartnerFormProps) { id: props.initialValues?.id, image: props.initialValues?.image, name: props.initialValues?.name, - description: props.initialValues?.description + description: props.initialValues?.description, + isPublic: props.initialValues?.isPublic }} onSubmit={onSubmit} autoComplete="off" @@ -76,6 +77,8 @@ export default function PartnerForm(props: PartnerFormProps) { + + ); } diff --git a/app/components/forms/validations.ts b/app/components/forms/validations.ts index fd8e6f23..877bccc0 100644 --- a/app/components/forms/validations.ts +++ b/app/components/forms/validations.ts @@ -77,7 +77,8 @@ export const ClubInput = z facebookURL: z.string().url().optional().nullable(), twitterURL: z.string().url().optional().nullable(), instagramURL: z.string().url().optional().nullable(), - customURL: z.string().url().optional().nullable() + customURL: z.string().url().optional().nullable(), + isPublic: z.boolean().optional().nullable() }) .partial(); export type ClubInputType = z.infer; @@ -113,7 +114,8 @@ export const PartnerInput = z .optional() .nullable(), name: z.string().max(255), - description: z.string().max(3000).optional().nullable() + description: z.string().max(3000).optional().nullable(), + isPublic: z.boolean().optional().nullable() }) .partial(); export type PartnerInputType = z.infer; diff --git a/app/components/hub/transactions/display/Adherent.tsx b/app/components/hub/transactions/display/Adherent.tsx new file mode 100644 index 00000000..3045152f --- /dev/null +++ b/app/components/hub/transactions/display/Adherent.tsx @@ -0,0 +1,20 @@ +import Typography, { TypographyTypeMap } from '@mui/material/Typography'; + +import { useQuery } from '@blitzjs/rpc'; + +type BalanceProps = { + getQuery: any; + queryArgs?: any; + variant?: TypographyTypeMap['props']['variant']; +}; + +export default function Adherent({ getQuery, queryArgs = {}, variant = 'h5' }: BalanceProps) { + const [user] = useQuery(getQuery, queryArgs); + const adherent = (user as any).is_member; + + return ( + + {adherent ? 'Cotisant' : 'Non-Cotisant'} + + ); +} diff --git a/app/components/hub/transactions/display/TransactionsCard.tsx b/app/components/hub/transactions/display/TransactionsCard.tsx index e1667c69..4d0b4a7b 100644 --- a/app/components/hub/transactions/display/TransactionsCard.tsx +++ b/app/components/hub/transactions/display/TransactionsCard.tsx @@ -1,6 +1,5 @@ import { Suspense } from 'react'; -import Badge from '@mui/material/Badge'; import Button from '@mui/material/Button'; import ButtonGroup from '@mui/material/ButtonGroup'; import Card from '@mui/material/Card'; @@ -9,7 +8,6 @@ import Typography from '@mui/material/Typography'; import CompareArrows from '@mui/icons-material/CompareArrowsTwoTone'; import History from '@mui/icons-material/HistoryTwoTone'; -import LocalAtm from '@mui/icons-material/LocalAtmTwoTone'; import { useAuthenticatedSession } from '@blitzjs/auth'; @@ -17,7 +15,7 @@ import Balance from 'app/components/hub/transactions/display/Balance'; import RecentTransactions from 'app/components/hub/transactions/display/RecentTransactions'; import getCurrentUser from 'app/entities/users/queries/getCurrentUser'; -export default function TransactionsCard({ openTransfer, openHistory, openTopUp }) { +export default function TransactionsCard({ openTransfer, openHistory }) { const session = useAuthenticatedSession(); const FallbackComponent = [...Array(10).keys()].map((x) => ( @@ -53,18 +51,6 @@ export default function TransactionsCard({ openTransfer, openHistory, openTopUp Historique - - - - ); diff --git a/app/components/hub/transactions/operations/topUp/TopUp.tsx b/app/components/hub/transactions/operations/topUp/TopUp.tsx deleted file mode 100644 index a17a7ddb..00000000 --- a/app/components/hub/transactions/operations/topUp/TopUp.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useState } from 'react'; - -import { useAuthenticatedSession } from '@blitzjs/auth'; -import { useMutation } from '@blitzjs/rpc'; - -import { TopUpInputType } from 'app/components/forms/validations'; -import Snackbar from 'app/core/layouts/Snackbar'; -import useSnackbar from 'app/entities/hooks/useSnackbar'; -import requestTopUp, { PaymentMethod } from 'app/entities/transactions/mutations/requestTopUp'; - -import TopUpForm from './TopUpForm'; - -export default function TopUp() { - const { open, message, severity, onClose, onShow } = useSnackbar(); - const [paymentMethod, setPaymentMethod] = useState('credit'); - const [topUp] = useMutation(requestTopUp); - - const beforeSubmit = (paymentMethod: PaymentMethod) => () => setPaymentMethod(paymentMethod); - - const onSuccess = (data: TopUpInputType) => { - topUp({ - amount: data.amount, - method: paymentMethod - }).then( - (url) => { - window.location.assign(url as string); - }, - (error) => { - onShow('error', error.message); - } - ); - }; - - return ( - <> - - - - - ); -} diff --git a/app/components/hub/transactions/operations/topUp/TopUpDialog.tsx b/app/components/hub/transactions/operations/topUp/TopUpDialog.tsx deleted file mode 100644 index 40b83809..00000000 --- a/app/components/hub/transactions/operations/topUp/TopUpDialog.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import IconButton from '@mui/material/IconButton'; -import NoSsr from '@mui/material/NoSsr'; - -import Close from '@mui/icons-material/CloseTwoTone'; - -import TopUp from 'app/components/hub/transactions/operations/topUp/TopUp'; -import SlideTransition from 'app/core/layouts/SlideTransition'; -import { useMediaQuery } from 'app/core/styles/theme'; - -type TopUpDialogProps = { - isOpen: boolean; - onClose: () => void; -}; - -export default function TopUpDialog({ isOpen, onClose }: TopUpDialogProps) { - const fullScreen = useMediaQuery('md'); - - return ( - - - - - - - - - - - - - - ); -} diff --git a/app/components/hub/transactions/operations/topUp/TopUpForm.tsx b/app/components/hub/transactions/operations/topUp/TopUpForm.tsx deleted file mode 100644 index f3a5a3e6..00000000 --- a/app/components/hub/transactions/operations/topUp/TopUpForm.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; - -import Image from 'next/image'; - -import EnhancedTextField from 'app/components/forms/EnhancedTextfield'; -import { FORM_ERROR, Form } from 'app/components/forms/Form'; -import { TopUpInput, TopUpInputType } from 'app/components/forms/validations'; -import { PaymentMethod } from 'app/entities/transactions/mutations/requestTopUp'; - -type TopUpFormProps = { - onSuccess: (values: TopUpInputType) => void; - beforeSubmit: (paymentMethod: PaymentMethod) => () => void; -}; - -export default function TopUpForm(props: TopUpFormProps) { - const onSubmit = async (values) => { - try { - await props.onSuccess(values); - } catch (error) { - return { - [FORM_ERROR]: 'Sorry, we had an unexpected error. Please try again. - ' + error.toString() - }; - } - }; - - return ( -
- - -
- - - -
- - - Si vous rencontrez un problème lors de votre rechargement, contactez un membre BDE - - - ); -} diff --git a/app/components/public/Clubs.tsx b/app/components/public/Clubs.tsx index 7ebe9c8b..0ca0229b 100644 --- a/app/components/public/Clubs.tsx +++ b/app/components/public/Clubs.tsx @@ -9,7 +9,7 @@ import { Club } from 'db'; import Image from 'next/image'; -import getClubs from 'app/entities/clubs/queries/getClubs'; +import getPublicClubs from 'app/entities/clubs/queries/getPublicClubs'; import Carousel from './carousel'; @@ -46,7 +46,7 @@ export default function Clubs() { }> - getQuery={getClubs} queryKey="clubs" /> + getQuery={getPublicClubs} queryKey="clubs" /> ); diff --git a/app/components/public/Partners.tsx b/app/components/public/Partners.tsx index 6f465686..39ad48d9 100644 --- a/app/components/public/Partners.tsx +++ b/app/components/public/Partners.tsx @@ -10,7 +10,7 @@ import { Partner } from 'db'; import Image from 'next/image'; import Link from 'app/core/lib/Link'; -import getPartners from 'app/entities/partners/queries/getPartners'; +import getPublicPartners from 'app/entities/partners/queries/getPublicPartners'; import Carousel from './carousel'; @@ -52,7 +52,7 @@ export default function Partners() { }> - getQuery={getPartners} queryKey="partners" /> + getQuery={getPublicPartners} queryKey="partners" /> ); diff --git a/app/core/utils/topup.ts b/app/core/utils/topup.ts deleted file mode 100644 index 3ed675fb..00000000 --- a/app/core/utils/topup.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { BinaryLike, KeyObject, createHmac } from 'crypto'; - -export function makeMerchantReference(card: number, timestamp: number) { - const card_prefix = card > 0 ? 'p' : 'm'; - - return `r${card_prefix}${Math.abs(card)}t${Math.ceil(timestamp)}`; -} - -export function makeShopOrderReference(card: number, amount: number) { - const card_prefix = card > 0 ? 'p' : 'm'; - - return `o${card_prefix}${Math.abs(card)}a${amount}`; -} - -export function makeHmac(elements: any[], secret: BinaryLike | KeyObject) { - return createHmac('sha1', secret).update(elements.join('*')).digest('hex'); -} - -export type TopUpInfo = { - userId: string; - card: number; - amount: number; - reference: string; - orderReference: string; - creationDate: number; - byCreditCard: boolean; -}; - -export function generateTopUpToken(info: TopUpInfo, secret: BinaryLike | KeyObject) { - let ret = Buffer.from(JSON.stringify(info)).toString('base64url'); - - return `${ret}.${createHmac('sha256', secret).update(ret).digest('hex')}`; -} - -export function parseTopUpToken(token: string, secret: BinaryLike | KeyObject) { - let data = token.split('.'); - - if (data.length != 2) return null; - - if (data[1] != createHmac('sha256', secret).update(data[0]).digest('hex')) return null; - - try { - return JSON.parse(Buffer.from(data[0], 'base64url').toString('utf8')); - } catch { - return null; - } -} diff --git a/app/entities/clubs/queries/getClubs.ts b/app/entities/clubs/queries/getClubs.ts index ee201e8f..287e8a7a 100644 --- a/app/entities/clubs/queries/getClubs.ts +++ b/app/entities/clubs/queries/getClubs.ts @@ -4,40 +4,25 @@ import { resolver } from '@blitzjs/rpc'; type GetClubsInput = Pick; -export default resolver.pipe(async ({ where, orderBy, skip = 0, take }: GetClubsInput, ctx) => { - console.log(ctx.session.roles); - - if (ctx.session.roles?.includes('*') != true && ctx.session.role?.includes('bde') != true) { - const limit: Prisma.ClubWhereInput = { - NOT: { - OR: [{ name: 'listeux' }, { name: 'troll' }] - } +export default resolver.pipe( + resolver.authorize(['*', 'bde']), + async ({ where, orderBy, skip = 0, take }: GetClubsInput, _ctx) => { + const clubs = await db.club.findMany({ + where, + orderBy, + take, + skip + }); + + const count = await db.club.count({ where }); + const hasMore = typeof take === 'number' ? skip + take < count : false; + const nextPage = hasMore ? { take, skip: skip + take! } : null; + + return { + clubs, + nextPage, + hasMore, + count }; - - if (where) { - where = { - AND: [where, limit] - }; - } else { - where = limit; - } } - - const clubs = await db.club.findMany({ - where, - orderBy, - take, - skip - }); - - const count = await db.club.count({ where }); - const hasMore = typeof take === 'number' ? skip + take < count : false; - const nextPage = hasMore ? { take, skip: skip + take! } : null; - - return { - clubs, - nextPage, - hasMore, - count - }; -}); +); diff --git a/app/entities/clubs/queries/getPublicClubs.ts b/app/entities/clubs/queries/getPublicClubs.ts new file mode 100644 index 00000000..ebb24ed9 --- /dev/null +++ b/app/entities/clubs/queries/getPublicClubs.ts @@ -0,0 +1,23 @@ +import db from 'db'; + +import { resolver } from '@blitzjs/rpc'; + +type GetPublicClubsInput = {}; + +export default resolver.pipe(async ({}: GetPublicClubsInput, _ctx) => { + const clubs = await db.club.findMany({ + where: { + name: { + notIn: ['*', 'listeux'] + }, + isPublic: true + }, + orderBy: { + name: 'asc' + } + }); + + return { + clubs + }; +}); diff --git a/app/entities/partners/queries/getPartners.ts b/app/entities/partners/queries/getPartners.ts index d1010d86..1923d2f6 100644 --- a/app/entities/partners/queries/getPartners.ts +++ b/app/entities/partners/queries/getPartners.ts @@ -4,22 +4,25 @@ import { resolver } from '@blitzjs/rpc'; type GetPartnersInput = Pick; -export default resolver.pipe(async ({ where, orderBy, skip = 0, take }: GetPartnersInput) => { - const partners = await db.partner.findMany({ - where, - orderBy, - take, - skip - }); +export default resolver.pipe( + resolver.authorize(['*', 'bde']), + async ({ where, orderBy, skip = 0, take }: GetPartnersInput, _ctx) => { + const partners = await db.partner.findMany({ + where, + orderBy, + take, + skip + }); - const count = await db.partner.count({ where }); - const hasMore = typeof take === 'number' ? skip + take < count : false; - const nextPage = hasMore ? { take, skip: skip + take! } : null; + const count = await db.partner.count({ where }); + const hasMore = typeof take === 'number' ? skip + take < count : false; + const nextPage = hasMore ? { take, skip: skip + take! } : null; - return { - partners, - nextPage, - hasMore, - count - }; -}); + return { + partners, + nextPage, + hasMore, + count + }; + } +); diff --git a/app/entities/partners/queries/getPublicPartners.ts b/app/entities/partners/queries/getPublicPartners.ts new file mode 100644 index 00000000..2382f809 --- /dev/null +++ b/app/entities/partners/queries/getPublicPartners.ts @@ -0,0 +1,20 @@ +import db, { Prisma } from 'db'; + +import { resolver } from '@blitzjs/rpc'; + +type GetPublicPartnersInput = {}; + +export default resolver.pipe(async ({}: GetPublicPartnersInput) => { + const partners = await db.partner.findMany({ + where: { + isPublic: true + }, + orderBy: { + name: 'asc' + } + }); + + return { + partners + }; +}); diff --git a/app/entities/transactions/mutations/requestTopUp.ts b/app/entities/transactions/mutations/requestTopUp.ts deleted file mode 100644 index 05f3c4d1..00000000 --- a/app/entities/transactions/mutations/requestTopUp.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Ctx } from 'blitz'; - -import { resolver } from '@blitzjs/rpc'; - -import { generateTopUpToken, makeHmac, makeMerchantReference, makeShopOrderReference } from 'app/core/utils/topup'; - -type RequestTopUpInput = { - amount: number; - method: PaymentMethod; -}; - -export type PaymentMethod = 'credit' | 'lyf'; - -function generateHmac(data: URLSearchParams, additionalData: string, byCreditCard: boolean): string { - let list = [data.get('lang')]; - - let common = [ - data.get('posUuid'), - data.get('shopReference'), - data.get('shopOrderReference'), - data.get('deliveryFeesAmount'), - data.get('amount'), - data.get('currency') - ]; - - if (byCreditCard) { - list = list.concat(common); - list = list.concat([ - data.get('onSuccess'), - data.get('onError'), - additionalData, - data.get('callBackRequired'), - data.get('mode'), - data.get('address'), - data.get('city'), - data.get('country'), - data.get('zipCode') - ]); - } else { - list = list.concat([data.get('version'), data.get('timestamp')]); - list = list.concat(common); - list = list.concat([ - data.get('mode'), - data.get('onSuccess'), - data.get('onCancel'), - data.get('onError'), - additionalData, - data.get('enforcedIdentification') - ]); - } - - return makeHmac(list, `${process.env.LYF_API_SECRET_KEY}`); -} - -function prepareRequest(id: string, card: number, amount: number, byCreditCard: boolean): URLSearchParams { - const body = new URLSearchParams(); - - const timestamp = Math.floor(+new Date() / 1000); - const tAmount = Math.round(amount * 100); - - const shopReference = makeMerchantReference(card, timestamp); - const shopOrderReference = makeShopOrderReference(card, tAmount); - - const token = generateTopUpToken( - { - userId: id, - card, - amount: tAmount, - reference: shopReference, - orderReference: shopOrderReference, - creationDate: timestamp, - byCreditCard - }, - `${process.env.SESSION_SECRET_KEY}` - ); - - const userCallbackUrl = `${process.env.NEXT_PUBLIC_FRONTEND_URL}/hub`; - const additionalData = `{"callbackUrl":"${process.env.NEXT_PUBLIC_FRONTEND_URL}/api/topup/${token}"}`; - - // Common request fields - body.append('lang', 'fr'); - body.append('version', 'v2.0'); - body.append('posUuid', `${process.env.LYF_API_VENDOR_ID}`); - body.append('shopReference', shopReference); - body.append('shopOrderReference', shopOrderReference); - body.append('mode', 'IMMEDIATE'); - body.append('amount', `${tAmount}`); - body.append('deliveryFeesAmount', '0'); - body.append('currency', 'EUR'); - body.append('onSuccess', userCallbackUrl); - body.append('onError', userCallbackUrl); - body.append('additionalDataEncoded', Buffer.from(additionalData).toString('base64')); - - // Method-specific fields - if (byCreditCard) { - body.append('caseNumber', '01234'); - body.append('callBackRequired', 'true'); - body.append('country', 'FR'); - - if (process.env.NODE_ENV === 'development') { - body.append('address', '7 Some Street'); - body.append('city', 'Bigcity'); - body.append('zipCode', '01234'); - } else { - body.append('address', ''); - body.append('city', ''); - body.append('zipCode', ''); - } - } else { - body.append('onCancel', userCallbackUrl); - body.append('timestamp', `${timestamp}`); - body.append('enforcedIdentification', 'false'); - } - - body.append('mac', generateHmac(body, additionalData, byCreditCard)); - - return body; -} - -export default resolver.pipe(resolver.authorize(), async (input: RequestTopUpInput, ctx: Ctx) => { - if (Number.isNaN(input.amount) || input.amount <= 0 || input.amount >= 1000) { - throw new Error('Valeur invalide'); - } - - const byCreditCard = input.method == 'credit'; - const req = prepareRequest(ctx.session.userId as string, ctx.session.card as number, input.amount, byCreditCard); - - return `${ - byCreditCard ? process.env.LYF_CREDIT_CARD_API_URL : process.env.LYF_FROM_APPLICATION_API_URL - }?${req.toString()}`; -}); diff --git a/db/schema.prisma b/db/schema.prisma index 99e64b53..09daf707 100644 --- a/db/schema.prisma +++ b/db/schema.prisma @@ -115,6 +115,8 @@ model Club { instagramURL String? customURL String? + isPublic Boolean @default(true) + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Event Event[] @relation("EventClub") @@ -127,6 +129,8 @@ model Partner { description String? image String? + isPublic Boolean @default(true) + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/db/seeds/users.ts b/db/seeds/users.ts index 824ddab1..a107bb61 100644 --- a/db/seeds/users.ts +++ b/db/seeds/users.ts @@ -26,7 +26,7 @@ const users = async (db) => { firstname: 'Venceslas', nickname: 'venny', image: 'https://i.imgur.com/VbdBkxz.png', - email: 'duet.venceslas@orange.fr', + email: 'contact@citorva.fr', card: 1463, balance: 0, roles: '*', diff --git a/package.json b/package.json index 287a19ee..c2fe3cc8 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@mui/x-date-pickers": "^5.0.13", "@prisma/client": "^4.8.1", "blitz": "2.0.0-beta.21", + "caniuse-lite": "^1.0.30001553", "chart.js": "3.9.1", "cuid": "^2.1.8", "date-fns": "^2.29.3", diff --git a/pages/api/topup/[token].ts b/pages/api/topup/[token].ts deleted file mode 100644 index 25fb0245..00000000 --- a/pages/api/topup/[token].ts +++ /dev/null @@ -1,107 +0,0 @@ -import db from 'db'; -import { NextApiRequest, NextApiResponse } from 'next'; - -import { makeHmac, makeMerchantReference, makeShopOrderReference, parseTopUpToken } from 'app/core/utils/topup'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === 'POST') { - const { body, query } = req; - - const token = query.token as string; - - const tokenInfo = parseTopUpToken(token, `${process.env.SESSION_SECRET_KEY}`); - - if (tokenInfo == null) { - res.status(404).send('BAD TOKEN'); - return; - } - - // Data retrieval - - const { - posUuid, - shopReference, - shopOrderReference, - amount, - discount, - currency, - status, - creationDate, - transactionUuid, - additionalData, - mac - } = body; - - // Verification of data authenticity - - const testMAC = makeHmac( - [ - posUuid, - shopReference, - shopOrderReference, - amount, - discount, - currency, - status, - creationDate, - transactionUuid, - additionalData - ], - `${process.env.LYF_API_SECRET_KEY}` - ).toUpperCase(); - - if (testMAC != mac) { - res.status(400).send('BAD MAC'); - return; - } - - // Verification of data consistency - - if ( - posUuid != `${process.env.LYF_API_VENDOR_ID}` || - shopReference != makeMerchantReference(tokenInfo.card, tokenInfo.creationDate) || - shopOrderReference != makeShopOrderReference(tokenInfo.card, tokenInfo.amount) || - amount != tokenInfo.amount || - currency != 'EUR' || - ['VALIDATED', 'REFUSED'].indexOf(status) <= -1 - ) { - res.status(400).send('INCONSISTENT DATA'); - return; - } - - // Checking the status of the request - - if (status == 'VALIDATED') { - // Adding money to the user - - const user = await db.user.findUnique({ where: { id: tokenInfo.userId } }); - - if (user != null) { - const qAmount = amount / 100; - - await Promise.all([ - db.transaction.create({ - data: { - amount: qAmount, - description: `Rechargement +${qAmount}€`, - type: 'CREDIT', - user: { connect: { id: tokenInfo.userId } }, - prevBalance: user.balance - } - }), - db.user.update({ - where: { id: tokenInfo.userId }, - data: { balance: { increment: qAmount } } - }) - ]); - res.status(200).send('OK'); - } else { - res.status(500).send('UNKNOWN USER'); - } - } else { - res.status(200).send('OK'); - } - } else { - res.status(400).send('BAD REQUEST'); - } -} diff --git a/pages/dashboard/clubs.tsx b/pages/dashboard/clubs.tsx index 0bfeb705..810b2f22 100644 --- a/pages/dashboard/clubs.tsx +++ b/pages/dashboard/clubs.tsx @@ -1,3 +1,4 @@ +import { Checkbox } from '@mui/material'; import Avatar from '@mui/material/Avatar'; import GroupsIcon from '@mui/icons-material/Groups'; @@ -56,6 +57,11 @@ const columns = [ id: 'email', headerName: 'Email', searchCriteria: 'contains' + }, + { + id: 'isPublic', + headerName: 'Public', + render: (row) => } ]; diff --git a/pages/dashboard/partners.tsx b/pages/dashboard/partners.tsx index 0f2fee50..5d345a6a 100644 --- a/pages/dashboard/partners.tsx +++ b/pages/dashboard/partners.tsx @@ -1,3 +1,4 @@ +import { Checkbox } from '@mui/material'; import Avatar from '@mui/material/Avatar'; import AccountBalance from '@mui/icons-material/AccountBalance'; @@ -55,6 +56,11 @@ const columns = [ id: 'description', headerName: 'Description', searchCriteria: 'contains' + }, + { + id: 'isPublic', + headerName: 'Public', + render: (row) => } ]; diff --git a/pages/hub/index.tsx b/pages/hub/index.tsx index 32b9a06a..5f11bc07 100644 --- a/pages/hub/index.tsx +++ b/pages/hub/index.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; +import { Alert } from '@mui/material'; + import { BlitzPage, Routes } from '@blitzjs/next'; import Upcoming from 'app/components/hub/events/Upcoming'; @@ -7,14 +9,12 @@ import DiscordButton from 'app/components/hub/home/DiscordButton'; import News from 'app/components/hub/home/News'; import TransactionsCard from 'app/components/hub/transactions/display/TransactionsCard'; import HistoryDialog from 'app/components/hub/transactions/operations/history/HistoryDialog'; -import TopUpDialog from 'app/components/hub/transactions/operations/topUp/TopUpDialog'; import TransferDialog from 'app/components/hub/transactions/operations/transfer/TransferDialog'; import getHubNav from 'app/components/nav/hub/getHubNav'; const Hub: BlitzPage = () => { const [isTransferOpen, setIsTransferOpen] = useState(false); const [isHistoryOpen, setIsHistoryOpen] = useState(false); - const [isTopUpOpen, setIsTopUpOpen] = useState(false); const toggleDialog = (fn, open) => () => fn(open); @@ -24,6 +24,10 @@ const Hub: BlitzPage = () => { style={{ gridTemplateColumns: '1fr 310px' }} >
+ + Suite à des problèmes techniques, le rechargement en ligne a été temporairement désactivé. Veuillez + vous rapprocher d'un membre du BDE afin de recharger votre compte +
@@ -32,12 +36,10 @@ const Hub: BlitzPage = () => { - ); diff --git a/yarn.lock b/yarn.lock index aa68810f..7c65fd0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3013,9 +3013,14 @@ camelcase@^7.0.0: integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001426: - version "1.0.30001442" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz" - integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== + version "1.0.30001553" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz" + integrity sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A== + +caniuse-lite@^1.0.30001553: + version "1.0.30001672" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001672.tgz#02ac296ad4765c6c4f93031525f60cf8bdf4a44f" + integrity sha512-XhW1vRo1ob6aeK2w3rTohwTPBLse/rvjq+s3RTSBwnlZqoFFjx9cHsShJjAIbLsLjyoacaTxpLZy9v3gg6zypw== chalk@^2.0.0: version "2.4.2"