From 3e0f1a3146c54f0329cc30be41588d15dfe8b009 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 7 Jan 2025 18:16:15 +0000 Subject: [PATCH 01/22] feat: create referral middleware --- website/app/middleware/referral_middleware.ts | 15 +++++++++++++++ website/start/kernel.ts | 1 + 2 files changed, 16 insertions(+) create mode 100644 website/app/middleware/referral_middleware.ts diff --git a/website/app/middleware/referral_middleware.ts b/website/app/middleware/referral_middleware.ts new file mode 100644 index 0000000..cc43e8d --- /dev/null +++ b/website/app/middleware/referral_middleware.ts @@ -0,0 +1,15 @@ +import type { HttpContext } from '@adonisjs/core/http' +import type { NextFn } from '@adonisjs/core/types/http' + +export default class ReferralMiddleware { + handle(ctx: HttpContext, next: NextFn) { + const referralInput: string = ctx.request.input('ref') + + if (referralInput !== undefined) { + // If received referral, store as cookie + ctx.response.cookie('referral', [{ referralInput }]) + } + + return next() + } +} diff --git a/website/start/kernel.ts b/website/start/kernel.ts index 17fb18e..9246024 100644 --- a/website/start/kernel.ts +++ b/website/start/kernel.ts @@ -24,6 +24,7 @@ server.errorHandler(() => import('#exceptions/handler')) */ server.use([ () => import('#middleware/container_bindings_middleware'), + () => import('#middleware/referral_middleware'), () => import('@adonisjs/static/static_middleware'), () => import('@adonisjs/cors/cors_middleware'), () => import('@adonisjs/vite/vite_middleware'), From ca1b2ee1551ff7ae801b6b090767e3e4271973b4 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 7 Jan 2025 18:30:25 +0000 Subject: [PATCH 02/22] fix: move middleware from kernel to routes --- website/start/kernel.ts | 1 - website/start/routes.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/website/start/kernel.ts b/website/start/kernel.ts index 9246024..17fb18e 100644 --- a/website/start/kernel.ts +++ b/website/start/kernel.ts @@ -24,7 +24,6 @@ server.errorHandler(() => import('#exceptions/handler')) */ server.use([ () => import('#middleware/container_bindings_middleware'), - () => import('#middleware/referral_middleware'), () => import('@adonisjs/static/static_middleware'), () => import('@adonisjs/cors/cors_middleware'), () => import('@adonisjs/vite/vite_middleware'), diff --git a/website/start/routes.ts b/website/start/routes.ts index 51b9fb3..34184f6 100644 --- a/website/start/routes.ts +++ b/website/start/routes.ts @@ -10,6 +10,8 @@ import router from '@adonisjs/core/services/router' const TicketsController = () => import('#controllers/tickets_controller') +router.use([() => import('#middleware/referral_middleware')]) + router.on('/').renderInertia('home') router.get('/tickets', [TicketsController, 'index']) router.on('/tickets/:id/checkout').renderInertia('payments').as('checkout') From 477bf41e8d2144b59352df8801ce2b78d7c9c6ba Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 00:42:01 +0000 Subject: [PATCH 03/22] feat: name home path --- website/start/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/start/routes.ts b/website/start/routes.ts index 34184f6..2170e8f 100644 --- a/website/start/routes.ts +++ b/website/start/routes.ts @@ -12,6 +12,6 @@ const TicketsController = () => import('#controllers/tickets_controller') router.use([() => import('#middleware/referral_middleware')]) -router.on('/').renderInertia('home') +router.on('/').renderInertia('home').as('home') router.get('/tickets', [TicketsController, 'index']) router.on('/tickets/:id/checkout').renderInertia('payments').as('checkout') From 95ad174a2fb3dface3e777cc9375d1f7283a9d30 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 00:46:05 +0000 Subject: [PATCH 04/22] feat: referral service --- website/app/services/referral_service.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 website/app/services/referral_service.ts diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts new file mode 100644 index 0000000..39fd8c5 --- /dev/null +++ b/website/app/services/referral_service.ts @@ -0,0 +1,13 @@ +import Hashids from 'hashids' + +export default class HashIdService { + static hashIds = new Hashids('', 8) + + static encode(id: number) { + return HashIdService.hashIds.encode(id) + } + + static decode(hashId: string) { + return HashIdService.hashIds.decode(hashId)[0] + } +} From f332dab3fbe8fe47be58f44aa013b2d6b28f6196 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 00:46:46 +0000 Subject: [PATCH 05/22] feat: has_referral_link mixin --- .../app/models/mixins/has_referral_link.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 website/app/models/mixins/has_referral_link.ts diff --git a/website/app/models/mixins/has_referral_link.ts b/website/app/models/mixins/has_referral_link.ts new file mode 100644 index 0000000..8952187 --- /dev/null +++ b/website/app/models/mixins/has_referral_link.ts @@ -0,0 +1,27 @@ +import HashIdService from '#services/referral_service' +import { NormalizeConstructor } from '@adonisjs/core/types/helpers' +import { BaseModel } from '@adonisjs/lucid/orm' +import router from '@adonisjs/core/services/router' + +export const HasReferralLink = >( + superclass: T +) => { + return class extends superclass { + public getPromoterCode(): number { + throw new Error('Method getPromoterCode is not defined.') + } + + public getReferralCode = (): string => { + return HashIdService.encode(this.getPromoterCode()) + } + + public getReferralLink = (): string => { + return router + .builder() + .qs({ + ref: this.getReferralCode(), + }) + .make('home') + } + } +} From e186b304bf492ce5244d2d8816df861836572238 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 00:47:25 +0000 Subject: [PATCH 06/22] feat: added referral link to user --- website/app/models/user.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/website/app/models/user.ts b/website/app/models/user.ts index dfe4857..3212b76 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -3,13 +3,14 @@ import hash from '@adonisjs/core/services/hash' import { compose } from '@adonisjs/core/helpers' import { BaseModel, column } from '@adonisjs/lucid/orm' import { withAuthFinder } from '@adonisjs/auth/mixins/lucid' +import { HasReferralLink } from '#models/mixins/has_referral_link' const AuthFinder = withAuthFinder(() => hash.use('scrypt'), { uids: ['email'], passwordColumnName: 'password', }) -export default class User extends compose(BaseModel, AuthFinder) { +export default class User extends compose(BaseModel, AuthFinder, HasReferralLink) { @column({ isPrimary: true }) declare id: number @@ -27,4 +28,8 @@ export default class User extends compose(BaseModel, AuthFinder) { @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime | null + + getPromoterCode(): number { + return this.id + } } From e9bf0fe916b885d2ddb5f929d6f547b4db532a2a Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 11:25:20 +0000 Subject: [PATCH 07/22] feat: add hashids dependency --- website/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/website/package.json b/website/package.json index 3c34ff7..834152c 100644 --- a/website/package.json +++ b/website/package.json @@ -115,6 +115,7 @@ "date-fns": "^4.1.0", "edge.js": "^6.2.0", "embla-carousel-react": "^8.5.1", + "hashids": "^2.3.0", "input-otp": "^1.4.1", "lucide-react": "^0.468.0", "luxon": "^3.5.0", From 2a30fd1e7af74167d61cd49005379370002de54b Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 8 Jan 2025 12:50:39 +0000 Subject: [PATCH 08/22] fix: commit pnpm-lock.yaml --- website/pnpm-lock.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index 5b4ab3e..2314536 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -167,6 +167,9 @@ importers: embla-carousel-react: specifier: ^8.5.1 version: 8.5.1(react@19.0.0) + hashids: + specifier: ^2.3.0 + version: 2.3.0 input-otp: specifier: ^1.4.1 version: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -3769,6 +3772,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashids@2.3.0: + resolution: {integrity: sha512-ljM73TE/avEhNnazxaj0Dw3BbEUuLC5yYCQ9RSkSUcT4ZSU6ZebdKCIBJ+xT/DnSYW36E9k82GH1Q6MydSIosQ==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -10076,6 +10082,8 @@ snapshots: dependencies: has-symbols: 1.1.0 + hashids@2.3.0: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 From 12c3191d2f74ae4bada7a02bc0f6f7c39a7f2c51 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 22 Jan 2025 15:38:42 +0000 Subject: [PATCH 09/22] refactor: add type to HashIdService --- website/app/services/referral_service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index 39fd8c5..3fd2e35 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -3,11 +3,11 @@ import Hashids from 'hashids' export default class HashIdService { static hashIds = new Hashids('', 8) - static encode(id: number) { + static encode(id: number): string { return HashIdService.hashIds.encode(id) } - static decode(hashId: string) { - return HashIdService.hashIds.decode(hashId)[0] + static decode(hashId: string): number { + return HashIdService.hashIds.decode(hashId)[0] as number } } From 555737784f41c3a1adf96e2113ebb776642090ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Palma?= Date: Fri, 7 Feb 2025 16:36:43 +0000 Subject: [PATCH 10/22] feat: initial version of ReferralService started modelling participant info and promoter info --- .../app/controllers/referall_controller.ts | 3 ++ website/app/models/company_info.ts | 13 ++++++++ .../app/models/company_representative_info.ts | 18 +++++++++++ website/app/models/participant_info.ts | 18 +++++++++++ website/app/models/promoter_info.ts | 20 +++++++++++++ website/app/models/user.ts | 30 ++++++++++++++----- website/app/services/referral_service.ts | 22 ++++++++++++-- 7 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 website/app/controllers/referall_controller.ts create mode 100644 website/app/models/company_info.ts create mode 100644 website/app/models/company_representative_info.ts create mode 100644 website/app/models/participant_info.ts create mode 100644 website/app/models/promoter_info.ts diff --git a/website/app/controllers/referall_controller.ts b/website/app/controllers/referall_controller.ts new file mode 100644 index 0000000..0cc40ad --- /dev/null +++ b/website/app/controllers/referall_controller.ts @@ -0,0 +1,3 @@ +export default class ReferallController { + +} \ No newline at end of file diff --git a/website/app/models/company_info.ts b/website/app/models/company_info.ts new file mode 100644 index 0000000..9fb340e --- /dev/null +++ b/website/app/models/company_info.ts @@ -0,0 +1,13 @@ +import { DateTime } from 'luxon' +import { BaseModel, column } from '@adonisjs/lucid/orm' + +export default class CompanyInfo extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime +} \ No newline at end of file diff --git a/website/app/models/company_representative_info.ts b/website/app/models/company_representative_info.ts new file mode 100644 index 0000000..fe62970 --- /dev/null +++ b/website/app/models/company_representative_info.ts @@ -0,0 +1,18 @@ +import { DateTime } from 'luxon' +import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' +import User from './user.js' +import type { BelongsTo } from '@adonisjs/lucid/types/relations' + +export default class CompanyRepresentativeInfo extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime + + @belongsTo(() => User) + declare user: BelongsTo +} \ No newline at end of file diff --git a/website/app/models/participant_info.ts b/website/app/models/participant_info.ts new file mode 100644 index 0000000..cc0257a --- /dev/null +++ b/website/app/models/participant_info.ts @@ -0,0 +1,18 @@ +import { DateTime } from 'luxon' +import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' +import type { BelongsTo } from '@adonisjs/lucid/types/relations' +import User from './user.js' + +export default class ParticipantInfo extends BaseModel { + @column({ isPrimary: true }) + declare id: number + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime + + @belongsTo(() => User) + declare user: BelongsTo +} \ No newline at end of file diff --git a/website/app/models/promoter_info.ts b/website/app/models/promoter_info.ts new file mode 100644 index 0000000..438841c --- /dev/null +++ b/website/app/models/promoter_info.ts @@ -0,0 +1,20 @@ +import { DateTime } from 'luxon' +import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' +import User from './user.js' +import type { BelongsTo } from '@adonisjs/lucid/types/relations' +import { compose } from '@adonisjs/core/helpers' +import { HasReferralLink } from './mixins/has_referral_link.js' + +export default class PromoterInfo extends compose(BaseModel, HasReferralLink) { + @column({ isPrimary: true }) + declare id: number + + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime + + @belongsTo(() => User) + declare user: BelongsTo +} \ No newline at end of file diff --git a/website/app/models/user.ts b/website/app/models/user.ts index 49608d9..69a8fa3 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -1,11 +1,11 @@ -import { compose } from '@adonisjs/core/helpers' import { DateTime } from 'luxon' -import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm' +import { BaseModel, column, hasMany, hasOne } from '@adonisjs/lucid/orm' import Account from './account.js' -import type { HasMany } from '@adonisjs/lucid/types/relations' -import { HasReferralLink } from '#models/mixins/has_referral_link' +import type { HasMany, HasOne } from '@adonisjs/lucid/types/relations' +import PromoterInfo from './promoter_info.js' +import ParticipantInfo from './participant_info.js' -export default class User extends compose(BaseModel, HasReferralLink) { +export default class User extends BaseModel { @column({ isPrimary: true }) declare id: number @@ -24,11 +24,25 @@ export default class User extends compose(BaseModel, HasReferralLink) { @hasMany(() => Account) declare accounts: HasMany + @column() + declare referredBy: User + + @hasOne(() => PromoterInfo) + declare promoterInfo: HasOne + + @hasOne(() => ParticipantInfo) + declare participantInfo: HasOne + + isStudentAssociation() { + return this.promoterInfo !== null + } + + isParticipant() { + return this.participantInfo !== null + } + isEmailVerified() { return this.emailVerifiedAt !== null } - getPromoterCode(): number { - return this.id - } } \ No newline at end of file diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index 3fd2e35..591cdb1 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -1,13 +1,29 @@ +import User from '#models/user' import Hashids from 'hashids' -export default class HashIdService { +export default class ReferralService { static hashIds = new Hashids('', 8) static encode(id: number): string { - return HashIdService.hashIds.encode(id) + return ReferralService.hashIds.encode(id) } static decode(hashId: string): number { - return HashIdService.hashIds.decode(hashId)[0] as number + return ReferralService.hashIds.decode(hashId)[0] as number + } + + async handlePointAttribution(referredUser: User, referallCode: string) { + const promoterId = ReferralService.decode(referallCode) + if(!promoterId) return; + + const promoter = await User.find(promoterId) + if(!promoter) return; + + if(promoter.isStudentAssociation()) { + } else if(promoter.isParticipant()) { + + } + + referredUser.referredBy = promoter } } From faa787fea21ec7e18acdf6806729ece54ec64c51 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 9 Feb 2025 16:06:46 +0000 Subject: [PATCH 11/22] feat: Giving out points --- website/app/models/user.ts | 15 ++++-- website/app/services/referral_service.ts | 49 ++++++++++++++++--- .../1739036994584_alter_users_table.ts | 19 +++++++ 3 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 website/database/migrations/1739036994584_alter_users_table.ts diff --git a/website/app/models/user.ts b/website/app/models/user.ts index 69a8fa3..00e595a 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -1,7 +1,7 @@ import { DateTime } from 'luxon' -import { BaseModel, column, hasMany, hasOne } from '@adonisjs/lucid/orm' +import { BaseModel, belongsTo, column, hasMany, hasOne } from '@adonisjs/lucid/orm' import Account from './account.js' -import type { HasMany, HasOne } from '@adonisjs/lucid/types/relations' +import type { BelongsTo, HasMany, HasOne } from '@adonisjs/lucid/types/relations' import PromoterInfo from './promoter_info.js' import ParticipantInfo from './participant_info.js' @@ -25,7 +25,13 @@ export default class User extends BaseModel { declare accounts: HasMany @column() - declare referredBy: User + declare referredById: number | number + + @belongsTo(() => User) + declare referredBy: BelongsTo + + @column() + declare points: number @hasOne(() => PromoterInfo) declare promoterInfo: HasOne @@ -44,5 +50,4 @@ export default class User extends BaseModel { isEmailVerified() { return this.emailVerifiedAt !== null } - -} \ No newline at end of file +} diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index 591cdb1..c310db5 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -1,5 +1,6 @@ import User from '#models/user' import Hashids from 'hashids' +import db from '@adonisjs/lucid/services/db' export default class ReferralService { static hashIds = new Hashids('', 8) @@ -14,16 +15,48 @@ export default class ReferralService { async handlePointAttribution(referredUser: User, referallCode: string) { const promoterId = ReferralService.decode(referallCode) - if(!promoterId) return; + if (!promoterId) return const promoter = await User.find(promoterId) - if(!promoter) return; - - if(promoter.isStudentAssociation()) { - } else if(promoter.isParticipant()) { - + if (!promoter) return + + if (promoter.isStudentAssociation()) { + const trx = await db.transaction() + promoter.useTransaction(trx) + referredUser.useTransaction(trx) + + try { + promoter.points += 20 + await referredUser.related('referredBy').associate(promoter) + + await promoter.save() + await referredUser.save() + + trx.commit() + } catch { + trx.rollback() + } + } else if (promoter.isParticipant()) { + const trx = await db.transaction() + promoter.useTransaction(trx) + referredUser.useTransaction(trx) + + try { + const referralAssociation = await User.find(promoter.referredBy) + + if (referralAssociation !== null) { + referralAssociation.points += 20 + } + promoter.points += 10 + referredUser.referredBy = promoter.referredBy + + promoter.save() + referredUser.save() + + trx.commit() + } catch { + trx.rollback() + } } - - referredUser.referredBy = promoter } } diff --git a/website/database/migrations/1739036994584_alter_users_table.ts b/website/database/migrations/1739036994584_alter_users_table.ts new file mode 100644 index 0000000..a1b7896 --- /dev/null +++ b/website/database/migrations/1739036994584_alter_users_table.ts @@ -0,0 +1,19 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'users' + + async up() { + this.schema.alterTable(this.tableName, (table) => { + table.integer('points').defaultTo(0) + table.integer('referred_by').nullable() + }) + } + + async down() { + this.schema.alterTable(this.tableName, (table) => { + table.dropColumn('points') + table.dropColumn('referred_by') + }) + } +} From f2224b771a2e1615ae4ddee9ed32a05a23adaa42 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 9 Feb 2025 22:17:31 +0000 Subject: [PATCH 12/22] feat: implement transactions and info migrations --- .../app/controllers/referall_controller.ts | 3 - .../app/models/mixins/has_referral_link.ts | 4 +- website/app/models/participant_info.ts | 11 ++-- website/app/models/promoter_info.ts | 15 +++-- website/app/models/user.ts | 32 ++++++++--- website/app/services/referral_service.ts | 55 ++++++++----------- .../1739036994584_alter_users_table.ts | 6 +- ...39127665664_create_promoter_infos_table.ts | 18 ++++++ ...27688817_create_participant_infos_table.ts | 18 ++++++ .../1739128445636_alter_users_table.ts | 25 +++++++++ 10 files changed, 127 insertions(+), 60 deletions(-) delete mode 100644 website/app/controllers/referall_controller.ts create mode 100644 website/database/migrations/1739127665664_create_promoter_infos_table.ts create mode 100644 website/database/migrations/1739127688817_create_participant_infos_table.ts create mode 100644 website/database/migrations/1739128445636_alter_users_table.ts diff --git a/website/app/controllers/referall_controller.ts b/website/app/controllers/referall_controller.ts deleted file mode 100644 index 0cc40ad..0000000 --- a/website/app/controllers/referall_controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default class ReferallController { - -} \ No newline at end of file diff --git a/website/app/models/mixins/has_referral_link.ts b/website/app/models/mixins/has_referral_link.ts index 8952187..c1c78b9 100644 --- a/website/app/models/mixins/has_referral_link.ts +++ b/website/app/models/mixins/has_referral_link.ts @@ -1,7 +1,7 @@ -import HashIdService from '#services/referral_service' import { NormalizeConstructor } from '@adonisjs/core/types/helpers' import { BaseModel } from '@adonisjs/lucid/orm' import router from '@adonisjs/core/services/router' +import ReferralService from '#services/referral_service' export const HasReferralLink = >( superclass: T @@ -12,7 +12,7 @@ export const HasReferralLink = } public getReferralCode = (): string => { - return HashIdService.encode(this.getPromoterCode()) + return ReferralService.encode(this.getPromoterCode()) } public getReferralLink = (): string => { diff --git a/website/app/models/participant_info.ts b/website/app/models/participant_info.ts index cc0257a..9e97e28 100644 --- a/website/app/models/participant_info.ts +++ b/website/app/models/participant_info.ts @@ -1,18 +1,19 @@ import { DateTime } from 'luxon' -import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' -import type { BelongsTo } from '@adonisjs/lucid/types/relations' +import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' +import type { HasOne } from '@adonisjs/lucid/types/relations' import User from './user.js' export default class ParticipantInfo extends BaseModel { @column({ isPrimary: true }) declare id: number + @hasOne(() => User) + declare user: HasOne + @column.dateTime({ autoCreate: true }) declare createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime - @belongsTo(() => User) - declare user: BelongsTo -} \ No newline at end of file +} diff --git a/website/app/models/promoter_info.ts b/website/app/models/promoter_info.ts index 438841c..ed61de8 100644 --- a/website/app/models/promoter_info.ts +++ b/website/app/models/promoter_info.ts @@ -1,20 +1,19 @@ import { DateTime } from 'luxon' -import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' +import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' import User from './user.js' -import type { BelongsTo } from '@adonisjs/lucid/types/relations' -import { compose } from '@adonisjs/core/helpers' -import { HasReferralLink } from './mixins/has_referral_link.js' +import type { HasOne } from '@adonisjs/lucid/types/relations' -export default class PromoterInfo extends compose(BaseModel, HasReferralLink) { +export default class PromoterInfo extends BaseModel { @column({ isPrimary: true }) declare id: number + @hasOne(() => User) + declare user: HasOne + @column.dateTime({ autoCreate: true }) declare createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime - @belongsTo(() => User) - declare user: BelongsTo -} \ No newline at end of file +} diff --git a/website/app/models/user.ts b/website/app/models/user.ts index 00e595a..eabb790 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -4,8 +4,10 @@ import Account from './account.js' import type { BelongsTo, HasMany, HasOne } from '@adonisjs/lucid/types/relations' import PromoterInfo from './promoter_info.js' import ParticipantInfo from './participant_info.js' +import { compose } from '@adonisjs/core/helpers' +import { HasReferralLink } from './mixins/has_referral_link.js' -export default class User extends BaseModel { +export default class User extends compose(BaseModel, HasReferralLink) { @column({ isPrimary: true }) declare id: number @@ -25,29 +27,41 @@ export default class User extends BaseModel { declare accounts: HasMany @column() - declare referredById: number | number + declare referredById: number | null - @belongsTo(() => User) + @belongsTo(() => User, { + foreignKey: 'id' + }) declare referredBy: BelongsTo @column() declare points: number - @hasOne(() => PromoterInfo) - declare promoterInfo: HasOne + @column() + declare promoterInfoId: number | null + + @belongsTo(() => PromoterInfo) + declare promoterInfo: BelongsTo + + @column() + declare participantInfoId: number | null - @hasOne(() => ParticipantInfo) - declare participantInfo: HasOne + @belongsTo(() => ParticipantInfo) + declare participantInfo: BelongsTo isStudentAssociation() { - return this.promoterInfo !== null + return this.promoterInfoId !== null } isParticipant() { - return this.participantInfo !== null + return this.participantInfoId !== null } isEmailVerified() { return this.emailVerifiedAt !== null } + + public getPromoterCode: () => number = () => { + return this.id; + } } diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index c310db5..d093f80 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -3,6 +3,9 @@ import Hashids from 'hashids' import db from '@adonisjs/lucid/services/db' export default class ReferralService { + static POINTS_FOR_ASSOCIATION = 20 + static POINTS_FOR_PARTICIPANT = 10 + static hashIds = new Hashids('', 8) static encode(id: number): string { @@ -13,50 +16,40 @@ export default class ReferralService { return ReferralService.hashIds.decode(hashId)[0] as number } - async handlePointAttribution(referredUser: User, referallCode: string) { - const promoterId = ReferralService.decode(referallCode) + static async handlePointAttribution(referredUser: User, referralCode: string) { + const promoterId = ReferralService.decode(referralCode) if (!promoterId) return const promoter = await User.find(promoterId) if (!promoter) return if (promoter.isStudentAssociation()) { - const trx = await db.transaction() - promoter.useTransaction(trx) - referredUser.useTransaction(trx) + await db.transaction(async (trx) => { + promoter.useTransaction(trx) + referredUser.useTransaction(trx) - try { - promoter.points += 20 + promoter.points += this.POINTS_FOR_ASSOCIATION + // FIXME: Not associating await referredUser.related('referredBy').associate(promoter) - - await promoter.save() - await referredUser.save() - - trx.commit() - } catch { - trx.rollback() - } + }) } else if (promoter.isParticipant()) { - const trx = await db.transaction() - promoter.useTransaction(trx) - referredUser.useTransaction(trx) + console.log("promoter is participant") + const referralAssociation = await User.find(promoter.referredById) - try { - const referralAssociation = await User.find(promoter.referredBy) + // If the promoter was referred by a student association + // give points to the student association and to the + // promoter, else, give only to the promoter + await db.transaction(async (trx) => { + promoter.useTransaction(trx) + referredUser.useTransaction(trx) + referralAssociation?.useTransaction(trx) if (referralAssociation !== null) { - referralAssociation.points += 20 + referralAssociation.points += this.POINTS_FOR_ASSOCIATION + await referredUser.related('referredBy').associate(referralAssociation) } - promoter.points += 10 - referredUser.referredBy = promoter.referredBy - - promoter.save() - referredUser.save() - - trx.commit() - } catch { - trx.rollback() - } + promoter.points += this.POINTS_FOR_PARTICIPANT + }) } } } diff --git a/website/database/migrations/1739036994584_alter_users_table.ts b/website/database/migrations/1739036994584_alter_users_table.ts index a1b7896..7e53c77 100644 --- a/website/database/migrations/1739036994584_alter_users_table.ts +++ b/website/database/migrations/1739036994584_alter_users_table.ts @@ -6,14 +6,16 @@ export default class extends BaseSchema { async up() { this.schema.alterTable(this.tableName, (table) => { table.integer('points').defaultTo(0) - table.integer('referred_by').nullable() + table.integer('referred_by_id') + .references('id') + .inTable('users') }) } async down() { this.schema.alterTable(this.tableName, (table) => { table.dropColumn('points') - table.dropColumn('referred_by') + table.dropColumn('referred_by_id') }) } } diff --git a/website/database/migrations/1739127665664_create_promoter_infos_table.ts b/website/database/migrations/1739127665664_create_promoter_infos_table.ts new file mode 100644 index 0000000..86141be --- /dev/null +++ b/website/database/migrations/1739127665664_create_promoter_infos_table.ts @@ -0,0 +1,18 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'promoter_infos' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} diff --git a/website/database/migrations/1739127688817_create_participant_infos_table.ts b/website/database/migrations/1739127688817_create_participant_infos_table.ts new file mode 100644 index 0000000..099910f --- /dev/null +++ b/website/database/migrations/1739127688817_create_participant_infos_table.ts @@ -0,0 +1,18 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'participant_infos' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id') + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} \ No newline at end of file diff --git a/website/database/migrations/1739128445636_alter_users_table.ts b/website/database/migrations/1739128445636_alter_users_table.ts new file mode 100644 index 0000000..83669e6 --- /dev/null +++ b/website/database/migrations/1739128445636_alter_users_table.ts @@ -0,0 +1,25 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'users' + + async up() { + this.schema.alterTable(this.tableName, (table) => { + table.integer('promoter_info_id') + .unique() + .references('id') + .inTable('promoter_infos') + table.integer('participant_info_id') + .unique() + .references('id') + .inTable('participant_infos') + }) + } + + async down() { + this.schema.alterTable(this.tableName, (table) => { + table.dropColumn('promoter_info_id') + table.dropColumn('participant_info_id') + }) + } +} From 08225ef9e600a224308e35590f6da8711e4a7a6b Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 10 Feb 2025 18:45:18 +0000 Subject: [PATCH 13/22] feat: point attribution and referral by promoter --- website/app/models/participant_info.ts | 3 + website/app/models/promoter_info.ts | 3 + website/app/models/user.ts | 14 +++-- website/app/services/referral_service.ts | 56 +++++++++++-------- .../1739036994584_alter_users_table.ts | 7 ++- ...39127665664_create_promoter_infos_table.ts | 4 ++ ...27688817_create_participant_infos_table.ts | 6 +- 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/website/app/models/participant_info.ts b/website/app/models/participant_info.ts index 9e97e28..18542ee 100644 --- a/website/app/models/participant_info.ts +++ b/website/app/models/participant_info.ts @@ -7,6 +7,9 @@ export default class ParticipantInfo extends BaseModel { @column({ isPrimary: true }) declare id: number + @column() + declare userId: number | null + @hasOne(() => User) declare user: HasOne diff --git a/website/app/models/promoter_info.ts b/website/app/models/promoter_info.ts index ed61de8..b604859 100644 --- a/website/app/models/promoter_info.ts +++ b/website/app/models/promoter_info.ts @@ -7,6 +7,9 @@ export default class PromoterInfo extends BaseModel { @column({ isPrimary: true }) declare id: number + @column() + declare userId: number | null + @hasOne(() => User) declare user: HasOne diff --git a/website/app/models/user.ts b/website/app/models/user.ts index eabb790..e9c1a57 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -1,7 +1,7 @@ import { DateTime } from 'luxon' -import { BaseModel, belongsTo, column, hasMany, hasOne } from '@adonisjs/lucid/orm' +import { BaseModel, belongsTo, column, hasMany } from '@adonisjs/lucid/orm' import Account from './account.js' -import type { BelongsTo, HasMany, HasOne } from '@adonisjs/lucid/types/relations' +import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations' import PromoterInfo from './promoter_info.js' import ParticipantInfo from './participant_info.js' import { compose } from '@adonisjs/core/helpers' @@ -27,29 +27,31 @@ export default class User extends compose(BaseModel, HasReferralLink) { declare accounts: HasMany @column() - declare referredById: number | null + declare referredByPromoterId: number | null @belongsTo(() => User, { - foreignKey: 'id' + foreignKey: 'referredByPromoterId' }) - declare referredBy: BelongsTo + declare referredByPromoter: BelongsTo @column() declare points: number + // PromoterInfo @column() declare promoterInfoId: number | null @belongsTo(() => PromoterInfo) declare promoterInfo: BelongsTo + // ParticipantInfo @column() declare participantInfoId: number | null @belongsTo(() => ParticipantInfo) declare participantInfo: BelongsTo - isStudentAssociation() { + isPromoter() { return this.promoterInfoId !== null } diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index d093f80..4420830 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -3,7 +3,7 @@ import Hashids from 'hashids' import db from '@adonisjs/lucid/services/db' export default class ReferralService { - static POINTS_FOR_ASSOCIATION = 20 + static POINTS_FOR_PROMOTER = 20 static POINTS_FOR_PARTICIPANT = 10 static hashIds = new Hashids('', 8) @@ -17,38 +17,50 @@ export default class ReferralService { } static async handlePointAttribution(referredUser: User, referralCode: string) { - const promoterId = ReferralService.decode(referralCode) - if (!promoterId) return + // referredUser cannot be a promoter + if (referredUser.isPromoter()) + return - const promoter = await User.find(promoterId) - if (!promoter) return + const referralUserId = ReferralService.decode(referralCode) + if (!referralUserId) return - if (promoter.isStudentAssociation()) { + const referralUser = await User.find(referralUserId) + if (!referralUser) return + + if (referralUser.isPromoter()) { + + // If the referralUser is a promoter + // give points to the referralUser await db.transaction(async (trx) => { - promoter.useTransaction(trx) + referralUser.useTransaction(trx) referredUser.useTransaction(trx) - promoter.points += this.POINTS_FOR_ASSOCIATION - // FIXME: Not associating - await referredUser.related('referredBy').associate(promoter) + referralUser.points += this.POINTS_FOR_PROMOTER + + await referredUser.related('referredByPromoter').associate(referralUser) + referralUser.save() }) - } else if (promoter.isParticipant()) { - console.log("promoter is participant") - const referralAssociation = await User.find(promoter.referredById) + } else if (referralUser.isParticipant()) { + const referralPromoter: User | null = referralUser.referredByPromoterId !== null + ? await User.find(referralUser.referredByPromoterId) + : null; - // If the promoter was referred by a student association - // give points to the student association and to the - // promoter, else, give only to the promoter + // If the referralUser is a participant and was + // previously referred by a promoter, give points + // to the referralUser and to the promoter, else + // give only to the referralUser await db.transaction(async (trx) => { - promoter.useTransaction(trx) + referralUser.useTransaction(trx) referredUser.useTransaction(trx) - referralAssociation?.useTransaction(trx) + referralPromoter?.useTransaction(trx) - if (referralAssociation !== null) { - referralAssociation.points += this.POINTS_FOR_ASSOCIATION - await referredUser.related('referredBy').associate(referralAssociation) + referralUser.points += this.POINTS_FOR_PARTICIPANT + if (referralPromoter !== null && referralPromoter.isPromoter()) { + await referredUser.related('referredByPromoter').associate(referralPromoter) + referralPromoter.points += this.POINTS_FOR_PROMOTER + referralPromoter.save() } - promoter.points += this.POINTS_FOR_PARTICIPANT + await referralUser.save() }) } } diff --git a/website/database/migrations/1739036994584_alter_users_table.ts b/website/database/migrations/1739036994584_alter_users_table.ts index 7e53c77..9356131 100644 --- a/website/database/migrations/1739036994584_alter_users_table.ts +++ b/website/database/migrations/1739036994584_alter_users_table.ts @@ -6,16 +6,19 @@ export default class extends BaseSchema { async up() { this.schema.alterTable(this.tableName, (table) => { table.integer('points').defaultTo(0) - table.integer('referred_by_id') + table.integer('referred_by_promoter_id') + .unsigned() + .nullable() .references('id') .inTable('users') + .onDelete('SET NULL') }) } async down() { this.schema.alterTable(this.tableName, (table) => { table.dropColumn('points') - table.dropColumn('referred_by_id') + table.dropColumn('referred_by_promoter_id') }) } } diff --git a/website/database/migrations/1739127665664_create_promoter_infos_table.ts b/website/database/migrations/1739127665664_create_promoter_infos_table.ts index 86141be..9be240c 100644 --- a/website/database/migrations/1739127665664_create_promoter_infos_table.ts +++ b/website/database/migrations/1739127665664_create_promoter_infos_table.ts @@ -6,6 +6,10 @@ export default class extends BaseSchema { async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id') + table.integer('user_id') + .unique() + .references('id') + .inTable('users') table.timestamp('created_at') table.timestamp('updated_at') diff --git a/website/database/migrations/1739127688817_create_participant_infos_table.ts b/website/database/migrations/1739127688817_create_participant_infos_table.ts index 099910f..0cf6dd4 100644 --- a/website/database/migrations/1739127688817_create_participant_infos_table.ts +++ b/website/database/migrations/1739127688817_create_participant_infos_table.ts @@ -6,6 +6,10 @@ export default class extends BaseSchema { async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id') + table.integer('user_id') + .unique() + .references('id') + .inTable('users') table.timestamp('created_at') table.timestamp('updated_at') @@ -15,4 +19,4 @@ export default class extends BaseSchema { async down() { this.schema.dropTable(this.tableName) } -} \ No newline at end of file +} From 447289c55ac738fc5d06c79fb590e686eb83eadf Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 10 Feb 2025 19:46:19 +0000 Subject: [PATCH 14/22] chore: update pnpm-lock after merge --- website/pnpm-lock.yaml | 111 +++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 25 deletions(-) diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index 007ead1..0e11d63 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 5.0.2(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)) '@adonisjs/auth': specifier: ^9.3.1 - version: 9.3.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/lucid@21.6.0(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@vinejs/vine@3.0.0)(better-sqlite3@11.8.1)(luxon@3.5.0)(pg@8.13.1))(@adonisjs/session@7.5.1(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/redis@9.1.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)))(edge.js@6.2.1))(@japa/plugin-adonisjs@4.0.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@japa/runner@4.2.0)) + version: 9.3.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/lucid@21.6.0(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@vinejs/vine@3.0.0)(better-sqlite3@11.8.1)(luxon@3.5.0)(pg@8.13.1))(@adonisjs/session@7.5.1(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/redis@9.1.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)))(edge.js@6.2.1))(@japa/plugin-adonisjs@4.0.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@japa/runner@4.1.0)) '@adonisjs/core': specifier: ^6.17.1 version: 6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1) @@ -34,7 +34,7 @@ importers: version: 21.6.0(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@vinejs/vine@3.0.0)(better-sqlite3@11.8.1)(luxon@3.5.0)(pg@8.13.1) '@adonisjs/mail': specifier: ^9.2.2 - version: 9.2.2(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@aws-sdk/client-ses@3.734.0)(@types/luxon@3.4.2)(@types/node@22.12.0)(dayjs@1.11.13)(edge.js@6.2.1)(luxon@3.5.0)(moment@2.30.1) + version: 9.2.2(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@aws-sdk/client-ses@3.734.0)(@types/luxon@3.4.2)(@types/node@22.10.10)(dayjs@1.11.13)(edge.js@6.2.1)(luxon@3.5.0)(moment@2.30.1) '@adonisjs/redis': specifier: ^9.1.0 version: 9.1.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)) @@ -154,7 +154,7 @@ importers: version: 1.0.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-virtual': specifier: ^3.11.2 - version: 3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 3.13.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tuyau/client': specifier: ^0.2.4 version: 0.2.4 @@ -187,7 +187,7 @@ importers: version: 4.16.5 chrono-node: specifier: ^2.7.7 - version: 2.7.7 + version: 2.7.8 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -226,7 +226,10 @@ importers: version: 1.4.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) jotai: specifier: ^2.11.1 - version: 2.11.1(@types/react@19.0.8)(react@19.0.0) + version: 2.11.3(@types/react@19.0.8)(react@19.0.0) + leaflet-geosearch: + specifier: ^4.0.0 + version: 4.1.0 lucide-react: specifier: ^0.473.0 version: 0.473.0(react@19.0.0) @@ -308,7 +311,7 @@ importers: version: 4.0.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@japa/runner@4.1.0) '@japa/runner': specifier: ^4.1.0 - version: 4.2.0 + version: 4.1.0 '@shadx/cli': specifier: ^1.0.6 version: 1.0.6 @@ -1293,6 +1296,9 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@googlemaps/js-api-loader@1.16.8': + resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==} + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -2914,14 +2920,14 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tanstack/react-virtual@3.11.2': - resolution: {integrity: sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==} + '@tanstack/react-virtual@3.13.0': + resolution: {integrity: sha512-CchF0NlLIowiM2GxtsoKBkXA4uqSnY2KvnXo+kyUFD4a4ll6+J0qzoRsUPMwXV/H26lRsxgJIr/YmjYum2oEjg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/virtual-core@3.11.2': - resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==} + '@tanstack/virtual-core@3.13.0': + resolution: {integrity: sha512-NBKJP3OIdmZY3COJdWkSonr50FMVIi+aj5ZJ7hI/DTpEKg2RMfo/KvP8A3B/zOSpMgIe52B5E2yn7rryULzA6g==} '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -3515,8 +3521,8 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chrono-node@2.7.7: - resolution: {integrity: sha512-p3S7gotuTPu5oqhRL2p1fLwQXGgdQaRTtWR3e8Di9P1Pa9mzkK5DWR5AWBieMUh2ZdOnPgrK+zCrbbtyuA+D/Q==} + chrono-node@2.7.8: + resolution: {integrity: sha512-pzxemrTKu6jFVyAfkNxUckp9nlrmRFtr5lGrEJcVKyeKV9WSeGT78Oysazlzd/H0BdMv7EzACtJrw0pi2KODBQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} ci-info@4.1.0: @@ -4393,6 +4399,20 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + framer-motion@11.18.2: + resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -4697,6 +4717,9 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ifthenpay@0.3.4: + resolution: {integrity: sha512-CKafqjnTZpLrtqlPp4JVfUBK0zXLS4Da5U2pv8Dm3g1CCZfb1zCvttB0k8MUu0XtajPyBYtWLcDyqRhLqhQIPw==} + igniculus@1.5.0: resolution: {integrity: sha512-vhj2J/cSzNg2G5tcK4Z1KZdeYmQa5keoxFULUYAxctK/zHJb1oraO7noCqnJxKe1b2eZdiiaSL1IHPOFAI8UYQ==} engines: {node: '>=4.0.0'} @@ -4902,8 +4925,8 @@ packages: engines: {node: '>=0.10.30'} deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). - jotai@2.11.1: - resolution: {integrity: sha512-41Su098mpHIX29hF/XOpDb0SqF6EES7+HXfrhuBqVSzRkxX48hD5i8nGsEewWZNAsBWJCTTmuz8M946Ih2PfcQ==} + jotai@2.11.3: + resolution: {integrity: sha512-B/PsewAQ0UOS5e2+TTWegUPQ3SCLPCjPY24LYUjfn2EorGlluTA2dFjVLgF1+xHLjK9Jit3y5mKHyMG3Xq/GZg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=17.0.0' @@ -5036,6 +5059,12 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + leaflet-geosearch@4.1.0: + resolution: {integrity: sha512-HyqmRWInm5/B28PpAscruOFnYztamJji4OTsueNluvaHg/hYVMWpaReyWvfVUQGVn8U42+Mm01gBpOcRsqh60g==} + + leaflet@1.9.4: + resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -5096,6 +5125,7 @@ packages: lodash.templatesettings@4.2.0: resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} + lodash@2.4.2: resolution: {integrity: sha512-Kak1hi6/hYHGVPmdyiZijoQyz5x2iGVzs6w9GYB/HiXEtylY7tIoYEROMjvM1d9nXJqPOrG2MNPMn01bJ+S0Rw==} engines: {'0': node, '1': rhino} @@ -5573,6 +5603,9 @@ packages: package-manager-detector@0.2.8: resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} + pad-start@1.0.2: + resolution: {integrity: sha512-EBN8Ez1SVRcZT1XsIE4WkdnZ5coLoaChkIgAET6gIlaLhXqCz9upVk0DQWFtOYkrpTVvbEppRUnqhTiJrBdkfw==} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -5907,6 +5940,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@2.4.2: @@ -7097,7 +7131,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@adonisjs/auth@9.3.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/lucid@21.6.0(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@vinejs/vine@3.0.0)(better-sqlite3@11.8.1)(luxon@3.5.0)(pg@8.13.1))(@adonisjs/session@7.5.1(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/redis@9.1.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)))(edge.js@6.2.1))(@japa/plugin-adonisjs@4.0.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@japa/runner@4.2.0))': + '@adonisjs/auth@9.3.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/lucid@21.6.0(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@vinejs/vine@3.0.0)(better-sqlite3@11.8.1)(luxon@3.5.0)(pg@8.13.1))(@adonisjs/session@7.5.1(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@adonisjs/redis@9.1.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)))(edge.js@6.2.1))(@japa/plugin-adonisjs@4.0.0(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@japa/runner@4.1.0))': dependencies: '@adonisjs/core': 6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1) '@adonisjs/presets': 2.6.4(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1)) @@ -7311,7 +7345,7 @@ snapshots: - supports-color - tedious - '@adonisjs/mail@9.2.2(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@aws-sdk/client-ses@3.734.0)(@types/luxon@3.4.2)(@types/node@22.12.0)(dayjs@1.11.13)(edge.js@6.2.1)(luxon@3.5.0)(moment@2.30.1)': + '@adonisjs/mail@9.2.2(@adonisjs/core@6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1))(@aws-sdk/client-ses@3.734.0)(@types/luxon@3.4.2)(@types/node@22.10.10)(dayjs@1.11.13)(edge.js@6.2.1)(luxon@3.5.0)(moment@2.30.1)': dependencies: '@adonisjs/core': 6.17.1(@adonisjs/assembler@7.8.2(typescript@5.7.3))(@vinejs/vine@3.0.0)(edge.js@6.2.1) '@poppinss/colors': 4.1.4 @@ -7322,7 +7356,7 @@ snapshots: fastq: 1.18.0 formdata-node: 6.0.3 got: 14.4.5 - ical-generator: 7.2.0(@types/luxon@3.4.2)(@types/node@22.12.0)(dayjs@1.11.13)(luxon@3.5.0)(moment@2.30.1) + ical-generator: 7.2.0(@types/luxon@3.4.2)(@types/node@22.10.10)(dayjs@1.11.13)(luxon@3.5.0)(moment@2.30.1) nodemailer: 6.10.0 optionalDependencies: '@aws-sdk/client-ses': 3.734.0 @@ -8207,6 +8241,9 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@googlemaps/js-api-loader@1.16.8': + optional: true + '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@19.0.0))': dependencies: react-hook-form: 7.54.2(react@19.0.0) @@ -9909,13 +9946,13 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tanstack/react-virtual@3.13.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/virtual-core': 3.11.2 + '@tanstack/virtual-core': 3.13.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@tanstack/virtual-core@3.11.2': {} + '@tanstack/virtual-core@3.13.0': {} '@tokenizer/token@0.3.0': {} @@ -10559,7 +10596,7 @@ snapshots: chownr@1.1.4: {} - chrono-node@2.7.7: + chrono-node@2.7.8: dependencies: dayjs: 1.11.13 @@ -11421,6 +11458,15 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@11.18.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + motion-dom: 11.18.1 + motion-utils: 11.18.1 + tslib: 2.8.1 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + fresh@0.5.2: {} fs-constants@1.0.0: {} @@ -11708,7 +11754,7 @@ snapshots: husky@9.1.7: {} - ical-generator@7.2.0(@types/luxon@3.4.2)(@types/node@22.12.0)(dayjs@1.11.13)(luxon@3.5.0)(moment@2.30.1): + ical-generator@7.2.0(@types/luxon@3.4.2)(@types/node@22.10.10)(dayjs@1.11.13)(luxon@3.5.0)(moment@2.30.1): dependencies: uuid-random: 1.3.2 optionalDependencies: @@ -11724,6 +11770,10 @@ snapshots: ieee754@1.2.1: {} + ifthenpay@0.3.4: + dependencies: + pad-start: 1.0.2 + igniculus@1.5.0: {} ignore@5.3.2: {} @@ -11883,7 +11933,7 @@ snapshots: moment: 2.30.1 topo: 1.1.0 - jotai@2.11.1(@types/react@19.0.8)(react@19.0.0): + jotai@2.11.3(@types/react@19.0.8)(react@19.0.0): optionalDependencies: '@types/react': 19.0.8 react: 19.0.0 @@ -11990,6 +12040,14 @@ snapshots: leac@0.6.0: {} + leaflet-geosearch@4.1.0: + optionalDependencies: + '@googlemaps/js-api-loader': 1.16.8 + leaflet: 1.9.4 + + leaflet@1.9.4: + optional: true + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -12057,6 +12115,7 @@ snapshots: lodash.templatesettings@4.2.0: dependencies: lodash._reinterpolate: 3.0.0 + lodash@2.4.2: {} lodash@4.17.21: {} @@ -12219,7 +12278,7 @@ snapshots: module-details-from-path@1.0.3: {} - moment@2.30.1: + moment@2.30.1: {} motion-dom@11.18.1: dependencies: @@ -12512,6 +12571,8 @@ snapshots: package-manager-detector@0.2.8: {} + pad-start@1.0.2: {} + pako@0.2.9: {} parent-module@1.0.1: @@ -13989,7 +14050,7 @@ snapshots: hoek: 2.16.3 joi: 4.9.0 - vite-plugin-restart@0.4.2(vite@6.0.11(@types/node@22.12.0)(jiti@1.21.7)(yaml@2.7.0)): + vite-plugin-restart@0.4.2(vite@6.0.11(@types/node@22.10.10)(jiti@1.21.7)(yaml@2.7.0)): dependencies: micromatch: 4.0.8 vite: 6.0.11(@types/node@22.10.10)(jiti@1.21.7)(yaml@2.7.0) From 129d1e1ee7f2d5f92194944869dd3de1c057a8c9 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 10 Feb 2025 20:11:06 +0000 Subject: [PATCH 15/22] fix: remove user_id from info models --- website/app/models/participant_info.ts | 3 --- website/app/models/promoter_info.ts | 3 --- .../migrations/1739127665664_create_promoter_infos_table.ts | 4 ---- .../1739127688817_create_participant_infos_table.ts | 4 ---- 4 files changed, 14 deletions(-) diff --git a/website/app/models/participant_info.ts b/website/app/models/participant_info.ts index 18542ee..9e97e28 100644 --- a/website/app/models/participant_info.ts +++ b/website/app/models/participant_info.ts @@ -7,9 +7,6 @@ export default class ParticipantInfo extends BaseModel { @column({ isPrimary: true }) declare id: number - @column() - declare userId: number | null - @hasOne(() => User) declare user: HasOne diff --git a/website/app/models/promoter_info.ts b/website/app/models/promoter_info.ts index b604859..ed61de8 100644 --- a/website/app/models/promoter_info.ts +++ b/website/app/models/promoter_info.ts @@ -7,9 +7,6 @@ export default class PromoterInfo extends BaseModel { @column({ isPrimary: true }) declare id: number - @column() - declare userId: number | null - @hasOne(() => User) declare user: HasOne diff --git a/website/database/migrations/1739127665664_create_promoter_infos_table.ts b/website/database/migrations/1739127665664_create_promoter_infos_table.ts index 9be240c..86141be 100644 --- a/website/database/migrations/1739127665664_create_promoter_infos_table.ts +++ b/website/database/migrations/1739127665664_create_promoter_infos_table.ts @@ -6,10 +6,6 @@ export default class extends BaseSchema { async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id') - table.integer('user_id') - .unique() - .references('id') - .inTable('users') table.timestamp('created_at') table.timestamp('updated_at') diff --git a/website/database/migrations/1739127688817_create_participant_infos_table.ts b/website/database/migrations/1739127688817_create_participant_infos_table.ts index 0cf6dd4..87eed33 100644 --- a/website/database/migrations/1739127688817_create_participant_infos_table.ts +++ b/website/database/migrations/1739127688817_create_participant_infos_table.ts @@ -6,10 +6,6 @@ export default class extends BaseSchema { async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id') - table.integer('user_id') - .unique() - .references('id') - .inTable('users') table.timestamp('created_at') table.timestamp('updated_at') From 9faf97344f8f1140f8d709135c786494c184861c Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 11 Feb 2025 23:17:22 +0000 Subject: [PATCH 16/22] chore: add postgres to the development docker compose --- docker-compose.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 64ae05b..60a626f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,6 +13,17 @@ services: - valkey-data:/data ports: - "6379:6379" + postgres: + image: postgres:latest + ports: + - "${POSTGRES_PORT}:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} volumes: - valkey-data: \ No newline at end of file + valkey-data: + postgres-data: From c4f26f4088af237047d97256f20c32e1cc7e15ec Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 11 Feb 2025 23:19:11 +0000 Subject: [PATCH 17/22] feat: use participant profile as info and create migrations --- website/app/models/participant_info.ts | 19 -------------- website/app/models/user.ts | 14 +++-------- ...27688817_create_participant_infos_table.ts | 18 ------------- .../1739128445636_alter_users_table.ts | 25 ------------------- ...9294506253_create_promoter_infos_table.ts} | 2 +- ....ts => 1739294511724_alter_users_table.ts} | 9 +++++++ 6 files changed, 13 insertions(+), 74 deletions(-) delete mode 100644 website/app/models/participant_info.ts delete mode 100644 website/database/migrations/1739127688817_create_participant_infos_table.ts delete mode 100644 website/database/migrations/1739128445636_alter_users_table.ts rename website/database/migrations/{1739127665664_create_promoter_infos_table.ts => 1739294506253_create_promoter_infos_table.ts} (99%) rename website/database/migrations/{1739036994584_alter_users_table.ts => 1739294511724_alter_users_table.ts} (78%) diff --git a/website/app/models/participant_info.ts b/website/app/models/participant_info.ts deleted file mode 100644 index 9e97e28..0000000 --- a/website/app/models/participant_info.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DateTime } from 'luxon' -import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' -import type { HasOne } from '@adonisjs/lucid/types/relations' -import User from './user.js' - -export default class ParticipantInfo extends BaseModel { - @column({ isPrimary: true }) - declare id: number - - @hasOne(() => User) - declare user: HasOne - - @column.dateTime({ autoCreate: true }) - declare createdAt: DateTime - - @column.dateTime({ autoCreate: true, autoUpdate: true }) - declare updatedAt: DateTime - -} diff --git a/website/app/models/user.ts b/website/app/models/user.ts index b19ac35..150c18c 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -3,7 +3,6 @@ import { BaseModel, belongsTo, column, hasMany } from '@adonisjs/lucid/orm' import Account from './account.js' import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations' import PromoterInfo from './promoter_info.js' -import ParticipantInfo from './participant_info.js' import { compose } from '@adonisjs/core/helpers' import { HasReferralLink } from './mixins/has_referral_link.js' import ParticipantProfile from './participant_profile.js' @@ -45,14 +44,7 @@ export default class User extends compose(BaseModel, HasReferralLink) { @belongsTo(() => PromoterInfo) declare promoterInfo: BelongsTo - // ParticipantInfo - @column() - declare participantInfoId: number | null - - @belongsTo(() => ParticipantInfo) - declare participantInfo: BelongsTo - - // Profiles + // ParticipantProfile @column() declare participantProfileId: number | null @@ -65,9 +57,9 @@ export default class User extends compose(BaseModel, HasReferralLink) { } isParticipant() { - return this.participantInfoId !== null + return this.participantProfileId !== null } - + isEmailVerified() { return this.emailVerifiedAt !== null } diff --git a/website/database/migrations/1739127688817_create_participant_infos_table.ts b/website/database/migrations/1739127688817_create_participant_infos_table.ts deleted file mode 100644 index 87eed33..0000000 --- a/website/database/migrations/1739127688817_create_participant_infos_table.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BaseSchema } from '@adonisjs/lucid/schema' - -export default class extends BaseSchema { - protected tableName = 'participant_infos' - - async up() { - this.schema.createTable(this.tableName, (table) => { - table.increments('id') - - table.timestamp('created_at') - table.timestamp('updated_at') - }) - } - - async down() { - this.schema.dropTable(this.tableName) - } -} diff --git a/website/database/migrations/1739128445636_alter_users_table.ts b/website/database/migrations/1739128445636_alter_users_table.ts deleted file mode 100644 index 83669e6..0000000 --- a/website/database/migrations/1739128445636_alter_users_table.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { BaseSchema } from '@adonisjs/lucid/schema' - -export default class extends BaseSchema { - protected tableName = 'users' - - async up() { - this.schema.alterTable(this.tableName, (table) => { - table.integer('promoter_info_id') - .unique() - .references('id') - .inTable('promoter_infos') - table.integer('participant_info_id') - .unique() - .references('id') - .inTable('participant_infos') - }) - } - - async down() { - this.schema.alterTable(this.tableName, (table) => { - table.dropColumn('promoter_info_id') - table.dropColumn('participant_info_id') - }) - } -} diff --git a/website/database/migrations/1739127665664_create_promoter_infos_table.ts b/website/database/migrations/1739294506253_create_promoter_infos_table.ts similarity index 99% rename from website/database/migrations/1739127665664_create_promoter_infos_table.ts rename to website/database/migrations/1739294506253_create_promoter_infos_table.ts index 86141be..731c9ba 100644 --- a/website/database/migrations/1739127665664_create_promoter_infos_table.ts +++ b/website/database/migrations/1739294506253_create_promoter_infos_table.ts @@ -15,4 +15,4 @@ export default class extends BaseSchema { async down() { this.schema.dropTable(this.tableName) } -} +} \ No newline at end of file diff --git a/website/database/migrations/1739036994584_alter_users_table.ts b/website/database/migrations/1739294511724_alter_users_table.ts similarity index 78% rename from website/database/migrations/1739036994584_alter_users_table.ts rename to website/database/migrations/1739294511724_alter_users_table.ts index 9356131..e2d88fb 100644 --- a/website/database/migrations/1739036994584_alter_users_table.ts +++ b/website/database/migrations/1739294511724_alter_users_table.ts @@ -5,7 +5,13 @@ export default class extends BaseSchema { async up() { this.schema.alterTable(this.tableName, (table) => { + table.integer('promoter_info_id') + .unique() + .references('id') + .inTable('promoter_infos') + table.integer('points').defaultTo(0) + table.integer('referred_by_promoter_id') .unsigned() .nullable() @@ -17,7 +23,10 @@ export default class extends BaseSchema { async down() { this.schema.alterTable(this.tableName, (table) => { + table.dropColumn('promoter_info_id') + table.dropColumn('points') + table.dropColumn('referred_by_promoter_id') }) } From dd9fe8f9ff8310bf94c005225d640177ded566e7 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 11 Feb 2025 23:50:16 +0000 Subject: [PATCH 18/22] feat: store referral user --- website/app/models/user.ts | 8 ++++++++ website/app/services/referral_service.ts | 4 ++++ .../migrations/1739294511724_alter_users_table.ts | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/website/app/models/user.ts b/website/app/models/user.ts index 150c18c..726ff9b 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -34,6 +34,14 @@ export default class User extends compose(BaseModel, HasReferralLink) { }) declare referredByPromoter: BelongsTo + @column() + declare referredByUserId: number | null + + @belongsTo(() => User, { + foreignKey: 'referredByUserId' + }) + declare referredByUser: BelongsTo + @column() declare points: number diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index 4420830..618a0ba 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -38,6 +38,7 @@ export default class ReferralService { referralUser.points += this.POINTS_FOR_PROMOTER await referredUser.related('referredByPromoter').associate(referralUser) + await referredUser.related('referredByUser').associate(referralUser) referralUser.save() }) } else if (referralUser.isParticipant()) { @@ -61,6 +62,9 @@ export default class ReferralService { referralPromoter.save() } await referralUser.save() + + await referredUser.related('referredByUser').associate(referralUser) + await referredUser.save() }) } } diff --git a/website/database/migrations/1739294511724_alter_users_table.ts b/website/database/migrations/1739294511724_alter_users_table.ts index e2d88fb..6e39d2c 100644 --- a/website/database/migrations/1739294511724_alter_users_table.ts +++ b/website/database/migrations/1739294511724_alter_users_table.ts @@ -18,6 +18,13 @@ export default class extends BaseSchema { .references('id') .inTable('users') .onDelete('SET NULL') + + table.integer('referred_by_user_id') + .unsigned() + .nullable() + .references('id') + .inTable('users') + .onDelete('SET NULL') }) } @@ -28,6 +35,8 @@ export default class extends BaseSchema { table.dropColumn('points') table.dropColumn('referred_by_promoter_id') + + table.dropColumn('referred_by_user_id') }) } } From b2e57ab64ba0f5f4ff0c776ecc19c911c3710d46 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 12 Feb 2025 00:07:36 +0000 Subject: [PATCH 19/22] feat: avoid duplicated referral --- website/app/models/user.ts | 4 ++++ website/app/services/referral_service.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/website/app/models/user.ts b/website/app/models/user.ts index 726ff9b..b28f668 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -72,6 +72,10 @@ export default class User extends compose(BaseModel, HasReferralLink) { return this.emailVerifiedAt !== null } + hasBeenReferred() { + return this.referredByUserId !== null; + } + public getPromoterCode: () => number = () => { return this.id; } diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index 618a0ba..b6946f5 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -21,6 +21,10 @@ export default class ReferralService { if (referredUser.isPromoter()) return + // cannot use a referral more than once + if (referredUser.hasBeenReferred()) + return + const referralUserId = ReferralService.decode(referralCode) if (!referralUserId) return From f1d52b5cd7c7ea981facb2c0da861cb656294c32 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 12 Feb 2025 01:19:45 +0000 Subject: [PATCH 20/22] fix: direct referrals awards more points --- website/app/services/referral_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index b6946f5..f89060f 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -62,7 +62,7 @@ export default class ReferralService { referralUser.points += this.POINTS_FOR_PARTICIPANT if (referralPromoter !== null && referralPromoter.isPromoter()) { await referredUser.related('referredByPromoter').associate(referralPromoter) - referralPromoter.points += this.POINTS_FOR_PROMOTER + referralPromoter.points += this.POINTS_FOR_PROMOTER - this.POINTS_FOR_PARTICIPANT referralPromoter.save() } await referralUser.save() From 3359d402e14472b92eed2ae48b54643c79980336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lima?= Date: Fri, 14 Feb 2025 10:16:00 +0000 Subject: [PATCH 21/22] feat: add frontend for referrals --- .../app/controllers/referrals_controller.ts | 34 ++++ website/app/cookies/referrals_cookies.ts | 3 + website/app/jobs/update_order_status.ts | 21 +- .../app/middleware/link_to_user_middleware.ts | 30 +++ website/app/middleware/referral_middleware.ts | 15 -- website/app/models/company_info.ts | 13 -- .../app/models/company_representative_info.ts | 18 -- .../app/models/mixins/has_referral_link.ts | 27 --- website/app/models/participant_profile.ts | 4 + .../{promoter_info.ts => promoter_profile.ts} | 3 +- website/app/models/user.ts | 61 ++++-- website/app/services/referral_service.ts | 186 ++++++++++++------ website/config/inertia.ts | 5 +- ...4506253_create_promoter_profiles_table.ts} | 6 +- .../1739294511724_alter_users_table.ts | 28 +-- ...417906_alter_participant_profiles_table.ts | 17 ++ .../database/seeders/2_account_seeder.dev.ts | 49 +++++ website/inertia/components/common/navbar.tsx | 21 +- website/inertia/components/common/page.tsx | 20 +- website/inertia/components/ui/label.tsx | 2 +- website/inertia/pages/referrals/page.tsx | 70 +++++++ website/lib/adonisjs/cookies.ts | 70 +++++++ website/package.json | 5 +- website/pnpm-lock.yaml | 92 +-------- website/start/kernel.ts | 4 +- website/start/routes.ts | 12 +- 26 files changed, 535 insertions(+), 281 deletions(-) create mode 100644 website/app/controllers/referrals_controller.ts create mode 100644 website/app/cookies/referrals_cookies.ts create mode 100644 website/app/middleware/link_to_user_middleware.ts delete mode 100644 website/app/middleware/referral_middleware.ts delete mode 100644 website/app/models/company_info.ts delete mode 100644 website/app/models/company_representative_info.ts delete mode 100644 website/app/models/mixins/has_referral_link.ts rename website/app/models/{promoter_info.ts => promoter_profile.ts} (89%) rename website/database/migrations/{1739294506253_create_promoter_infos_table.ts => 1739294506253_create_promoter_profiles_table.ts} (70%) create mode 100644 website/database/migrations/1739512417906_alter_participant_profiles_table.ts create mode 100644 website/database/seeders/2_account_seeder.dev.ts create mode 100644 website/inertia/pages/referrals/page.tsx create mode 100644 website/lib/adonisjs/cookies.ts diff --git a/website/app/controllers/referrals_controller.ts b/website/app/controllers/referrals_controller.ts new file mode 100644 index 0000000..400519d --- /dev/null +++ b/website/app/controllers/referrals_controller.ts @@ -0,0 +1,34 @@ +import type { HttpContext } from '@adonisjs/core/http' +import { referralCodeCookie } from '../cookies/referrals_cookies.js' +import ReferralService from '#services/referral_service' +import { inject } from '@adonisjs/core' + +@inject() +export default class ReferralsController { + constructor(private referralService: ReferralService) {} + + async showReferralLink(ctx: HttpContext) { + const user = ctx.auth.getUserOrFail() + const referralLink = await this.referralService.getReferralLink(user) + + return ctx.inertia.render('referrals', { referralLink }) + } + + async link(ctx: HttpContext) { + const referralCode = ctx.params.referralCode as string + const referrer = await this.referralService.getReferrerByCode(referralCode) + + ctx.logger.debug(referrer, "Referrer with code %s", referralCode) + + if (referrer) { + const user = ctx.auth.user + if (user) { + await this.referralService.linkUserToReferrer(user, referrer) + } else { + referralCodeCookie.set(ctx, referralCode, { maxAge: 7*24*3600 }) + } + } + + return ctx.response.redirect().toRoute('pages:home') + } +} diff --git a/website/app/cookies/referrals_cookies.ts b/website/app/cookies/referrals_cookies.ts new file mode 100644 index 0000000..c65a8de --- /dev/null +++ b/website/app/cookies/referrals_cookies.ts @@ -0,0 +1,3 @@ +import { TypedCookie } from "#lib/adonisjs/cookies.js"; + +export const referralCodeCookie = new TypedCookie('referrer') \ No newline at end of file diff --git a/website/app/jobs/update_order_status.ts b/website/app/jobs/update_order_status.ts index 140a936..4172d77 100644 --- a/website/app/jobs/update_order_status.ts +++ b/website/app/jobs/update_order_status.ts @@ -5,6 +5,8 @@ import { Job } from 'adonisjs-jobs' import ConfirmPaymentNotification from '#mails/confirm_payment_notification' import mail from '@adonisjs/mail/services/main' import db from '@adonisjs/lucid/services/db' +import app from '@adonisjs/core/services/app' +import User from '#models/user' type UpdateOrderStatusPayload = { requestId: string @@ -29,13 +31,18 @@ export default class UpdateOrderStatus extends Job { this.logger.info(`Order status is no longer pending: ${order.status}`) return // Exit if the status is no longer "Pending" } + const apiResponse = await axios.get( `https://api.ifthenpay.com/spg/payment/mbway/status?mbWayKey=${env.get('IFTHENPAY_MBWAY_KEY')}&requestId=${requestId}` ) if (apiResponse.status === 200) { - const status = apiResponse.data.Message + let status = apiResponse.data.Message if (status) { + if (app.inDev) { + status = "Success" + } + if (status === 'Pending') { await UpdateOrderStatus.dispatch({ requestId, email }, { delay: 10000 }) // Retry after 5 seconds this.logger.info(`Requeued job for requestId: ${requestId}`) @@ -54,7 +61,19 @@ export default class UpdateOrderStatus extends Job { const total = order.total const orderId = order.id + await mail.send(new ConfirmPaymentNotification(email, products, total, orderId)) + + const user = await User.find(order.userId) + if (user) { + await user.load('participantProfile') + const participantProfile = user.participantProfile + if (participantProfile) { + // FIXME - this is a hack + participantProfile.purchasedTicket = 'early-bird-with-housing' + await participantProfile.save() + } + } } } else { await UpdateOrderStatus.dispatch({ requestId, email }, { delay: 10000 }) // Retry after 5 seconds diff --git a/website/app/middleware/link_to_user_middleware.ts b/website/app/middleware/link_to_user_middleware.ts new file mode 100644 index 0000000..142be08 --- /dev/null +++ b/website/app/middleware/link_to_user_middleware.ts @@ -0,0 +1,30 @@ +import type { HttpContext } from '@adonisjs/core/http' +import type { NextFn } from '@adonisjs/core/types/http' +import { referralCodeCookie } from '../cookies/referrals_cookies.js' +import ReferralService from '#services/referral_service' +import { inject } from '@adonisjs/core' + +@inject() +export default class LinkToUserMiddleware { + constructor(private referralService: ReferralService) {} + + async handle(ctx: HttpContext, next: NextFn) { + const referralCode = referralCodeCookie.get(ctx) + + if (referralCode) { + if (!ctx.auth.authenticationAttempted) await ctx.auth.check() + + const user = ctx.auth.user + if (user) { + referralCodeCookie.clear(ctx) + + const referrer = await this.referralService.getReferrerByCode(referralCode) + if (referrer) { + await this.referralService.linkUserToReferrer(user, referrer) + } + } + } + + return next() + } +} diff --git a/website/app/middleware/referral_middleware.ts b/website/app/middleware/referral_middleware.ts deleted file mode 100644 index cc43e8d..0000000 --- a/website/app/middleware/referral_middleware.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { HttpContext } from '@adonisjs/core/http' -import type { NextFn } from '@adonisjs/core/types/http' - -export default class ReferralMiddleware { - handle(ctx: HttpContext, next: NextFn) { - const referralInput: string = ctx.request.input('ref') - - if (referralInput !== undefined) { - // If received referral, store as cookie - ctx.response.cookie('referral', [{ referralInput }]) - } - - return next() - } -} diff --git a/website/app/models/company_info.ts b/website/app/models/company_info.ts deleted file mode 100644 index 9fb340e..0000000 --- a/website/app/models/company_info.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DateTime } from 'luxon' -import { BaseModel, column } from '@adonisjs/lucid/orm' - -export default class CompanyInfo extends BaseModel { - @column({ isPrimary: true }) - declare id: number - - @column.dateTime({ autoCreate: true }) - declare createdAt: DateTime - - @column.dateTime({ autoCreate: true, autoUpdate: true }) - declare updatedAt: DateTime -} \ No newline at end of file diff --git a/website/app/models/company_representative_info.ts b/website/app/models/company_representative_info.ts deleted file mode 100644 index fe62970..0000000 --- a/website/app/models/company_representative_info.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DateTime } from 'luxon' -import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' -import User from './user.js' -import type { BelongsTo } from '@adonisjs/lucid/types/relations' - -export default class CompanyRepresentativeInfo extends BaseModel { - @column({ isPrimary: true }) - declare id: number - - @column.dateTime({ autoCreate: true }) - declare createdAt: DateTime - - @column.dateTime({ autoCreate: true, autoUpdate: true }) - declare updatedAt: DateTime - - @belongsTo(() => User) - declare user: BelongsTo -} \ No newline at end of file diff --git a/website/app/models/mixins/has_referral_link.ts b/website/app/models/mixins/has_referral_link.ts deleted file mode 100644 index c1c78b9..0000000 --- a/website/app/models/mixins/has_referral_link.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NormalizeConstructor } from '@adonisjs/core/types/helpers' -import { BaseModel } from '@adonisjs/lucid/orm' -import router from '@adonisjs/core/services/router' -import ReferralService from '#services/referral_service' - -export const HasReferralLink = >( - superclass: T -) => { - return class extends superclass { - public getPromoterCode(): number { - throw new Error('Method getPromoterCode is not defined.') - } - - public getReferralCode = (): string => { - return ReferralService.encode(this.getPromoterCode()) - } - - public getReferralLink = (): string => { - return router - .builder() - .qs({ - ref: this.getReferralCode(), - }) - .make('home') - } - } -} diff --git a/website/app/models/participant_profile.ts b/website/app/models/participant_profile.ts index 29f6757..18f48cb 100644 --- a/website/app/models/participant_profile.ts +++ b/website/app/models/participant_profile.ts @@ -17,6 +17,10 @@ export default class ParticipantProfile extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime + // Ticket Info + @column() + declare purchasedTicket: "early-bird-without-housing" | "early-bird-with-housing" | null + // General Info @column() diff --git a/website/app/models/promoter_info.ts b/website/app/models/promoter_profile.ts similarity index 89% rename from website/app/models/promoter_info.ts rename to website/app/models/promoter_profile.ts index ed61de8..2b0a68a 100644 --- a/website/app/models/promoter_info.ts +++ b/website/app/models/promoter_profile.ts @@ -3,7 +3,7 @@ import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' import User from './user.js' import type { HasOne } from '@adonisjs/lucid/types/relations' -export default class PromoterInfo extends BaseModel { +export default class PromoterProfile extends BaseModel { @column({ isPrimary: true }) declare id: number @@ -15,5 +15,4 @@ export default class PromoterInfo extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime - } diff --git a/website/app/models/user.ts b/website/app/models/user.ts index b28f668..e9bd56c 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -2,12 +2,10 @@ import { DateTime } from 'luxon' import { BaseModel, belongsTo, column, hasMany } from '@adonisjs/lucid/orm' import Account from './account.js' import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations' -import PromoterInfo from './promoter_info.js' -import { compose } from '@adonisjs/core/helpers' -import { HasReferralLink } from './mixins/has_referral_link.js' +import PromoterProfile from './promoter_profile.js' import ParticipantProfile from './participant_profile.js' -export default class User extends compose(BaseModel, HasReferralLink) { +export default class User extends BaseModel { @column({ isPrimary: true }) declare id: number @@ -26,33 +24,34 @@ export default class User extends compose(BaseModel, HasReferralLink) { @hasMany(() => Account) declare accounts: HasMany + // Referrals + @column() - declare referredByPromoterId: number | null + declare referringPromoterId: number | null @belongsTo(() => User, { - foreignKey: 'referredByPromoterId' + foreignKey: 'promoterId', }) - declare referredByPromoter: BelongsTo + declare referringPromoter: BelongsTo @column() - declare referredByUserId: number | null + declare referrerId: number | null @belongsTo(() => User, { - foreignKey: 'referredByUserId' + foreignKey: 'referrerId', }) - declare referredByUser: BelongsTo - - @column() - declare points: number + declare referrer: BelongsTo // PromoterInfo + @column() - declare promoterInfoId: number | null + declare promoterProfileId: number | null - @belongsTo(() => PromoterInfo) - declare promoterInfo: BelongsTo + @belongsTo(() => PromoterProfile) + declare promoterProfile: BelongsTo // ParticipantProfile + @column() declare participantProfileId: number | null @@ -60,8 +59,15 @@ export default class User extends compose(BaseModel, HasReferralLink) { declare participantProfile: BelongsTo // Functions + + get role() { + if (this.isParticipant()) return 'participant' as const + if (this.isPromoter()) return 'promoter' as const + return 'unknown' as const + } + isPromoter() { - return this.promoterInfoId !== null + return this.promoterProfileId !== null } isParticipant() { @@ -72,11 +78,24 @@ export default class User extends compose(BaseModel, HasReferralLink) { return this.emailVerifiedAt !== null } - hasBeenReferred() { - return this.referredByUserId !== null; + wasReferred() { + return this.referrerId !== null + } + + static async hasPurchasedTicket(user: User) { + if (!user.isParticipant()) return false + + await user.load('participantProfile') + return !!user.participantProfile.purchasedTicket + } + + static async getReferringPromoter(user: User) { + await user.load('referringPromoter') + return user.referringPromoter } - public getPromoterCode: () => number = () => { - return this.id; + static async getReferrer(user: User) { + await user.load('referrer') + return user.referrer } } diff --git a/website/app/services/referral_service.ts b/website/app/services/referral_service.ts index f89060f..f9cd2e6 100644 --- a/website/app/services/referral_service.ts +++ b/website/app/services/referral_service.ts @@ -1,75 +1,141 @@ import User from '#models/user' -import Hashids from 'hashids' -import db from '@adonisjs/lucid/services/db' +import Sqids from 'sqids' +import { buildUrl } from '../url.js' + +// const POINTS_FOR_PROMOTER = 20 +// const POINTS_FOR_PARTICIPANT = 10 + +const sqids = new Sqids({ + minLength: 8, +}) export default class ReferralService { - static POINTS_FOR_PROMOTER = 20 - static POINTS_FOR_PARTICIPANT = 10 + // Low-level encoding/decoding - static hashIds = new Hashids('', 8) + #encode(id: number) { + return sqids.encode([id]) + } - static encode(id: number): string { - return ReferralService.hashIds.encode(id) + #decode(hashId: string): number | null { + const values = sqids.decode(hashId) + + if (values.length !== 1) return null + return values[0] } - static decode(hashId: string): number { - return ReferralService.hashIds.decode(hashId)[0] as number + // To be moved to a policy once bouncer is installed + + async canUserRefer(user: User) { + if (user.isPromoter()) + return true + + return await User.hasPurchasedTicket(user) } - static async handlePointAttribution(referredUser: User, referralCode: string) { - // referredUser cannot be a promoter - if (referredUser.isPromoter()) - return + async canUserBeLinked(user: User) { + return !user.isPromoter() && !user.wasReferred() && !await User.hasPurchasedTicket(user) + } + + // High-level methods + + async #getReferralCode(user: User) { + if (!await this.canUserRefer(user)) { + return null + } + + return this.#encode(user.id) + } + + async getReferralLink(user: User) { + const referralCode = await this.#getReferralCode(user) + if (!referralCode) { + return null + } + + return buildUrl() + .params({ referralCode: referralCode }) + .make('actions:referrals.link') + } - // cannot use a referral more than once - if (referredUser.hasBeenReferred()) + async getReferrerByCode(referralCode: string): Promise { + const referralUserId = this.#decode(referralCode) + if (!referralUserId) return null + + const referrer = await User.find(referralUserId) + if (!referrer || !await this.canUserRefer(referrer)) return null + + return referrer + } + + async linkUserToReferrer(referredUser: User, referrer: User) { + if (!await this.canUserBeLinked(referredUser)) { return + } + + await referredUser.related('referrer').associate(referrer) - const referralUserId = ReferralService.decode(referralCode) - if (!referralUserId) return - - const referralUser = await User.find(referralUserId) - if (!referralUser) return - - if (referralUser.isPromoter()) { - - // If the referralUser is a promoter - // give points to the referralUser - await db.transaction(async (trx) => { - referralUser.useTransaction(trx) - referredUser.useTransaction(trx) - - referralUser.points += this.POINTS_FOR_PROMOTER - - await referredUser.related('referredByPromoter').associate(referralUser) - await referredUser.related('referredByUser').associate(referralUser) - referralUser.save() - }) - } else if (referralUser.isParticipant()) { - const referralPromoter: User | null = referralUser.referredByPromoterId !== null - ? await User.find(referralUser.referredByPromoterId) - : null; - - // If the referralUser is a participant and was - // previously referred by a promoter, give points - // to the referralUser and to the promoter, else - // give only to the referralUser - await db.transaction(async (trx) => { - referralUser.useTransaction(trx) - referredUser.useTransaction(trx) - referralPromoter?.useTransaction(trx) - - referralUser.points += this.POINTS_FOR_PARTICIPANT - if (referralPromoter !== null && referralPromoter.isPromoter()) { - await referredUser.related('referredByPromoter').associate(referralPromoter) - referralPromoter.points += this.POINTS_FOR_PROMOTER - this.POINTS_FOR_PARTICIPANT - referralPromoter.save() - } - await referralUser.save() - - await referredUser.related('referredByUser').associate(referralUser) - await referredUser.save() - }) + const referringPromoter = referrer.isPromoter() + ? referrer + : referrer.referringPromoterId !== null + ? await User.getReferringPromoter(referrer) + : null + + if (referringPromoter) { + await referredUser.related('referringPromoter').associate(referringPromoter) } } + + // To be moved to the points system, not implemented at the referral system level + // async handlePointAttribution(referredUser: User, referralCode: string) { + // // referredUser must be a participant + // if (!referredUser.isParticipant()) + // return + + // const referralUserId = ReferralService.decode(referralCode) + // if (!referralUserId) return + + // const referralUser = await User.find(referralUserId) + // if (!referralUser) return + + // if (referralUser.isPromoter()) { + + // // If the referralUser is a promoter + // // give points to the referralUser + // await db.transaction(async (trx) => { + // referralUser.useTransaction(trx) + // referredUser.useTransaction(trx) + + // referralUser.points += POINTS_FOR_PROMOTER + + // await referredUser.related('rootReferrer').associate(referralUser) + // await referredUser.related('referrer').associate(referralUser) + // referralUser.save() + // }) + // } else if (referralUser.isParticipant()) { + // const referralPromoter: User | null = referralUser.rootReferrerId !== null + // ? await User.find(referralUser.rootReferrerId) + // : null; + + // // If the referralUser is a participant and was + // // previously referred by a promoter, give points + // // to the referralUser and to the promoter, else + // // give only to the referralUser + // await db.transaction(async (trx) => { + // referralUser.useTransaction(trx) + // referredUser.useTransaction(trx) + // referralPromoter?.useTransaction(trx) + + // referralUser.points += POINTS_FOR_PARTICIPANT + // if (referralPromoter !== null && referralPromoter.isPromoter()) { + // await referredUser.related('referredByPromoter').associate(referralPromoter) + // referralPromoter.points += POINTS_FOR_PROMOTER - POINTS_FOR_PARTICIPANT + // referralPromoter.save() + // } + // await referralUser.save() + + // await referredUser.related('referredByUser').associate(referralUser) + // await referredUser.save() + // }) + // } + // } } diff --git a/website/config/inertia.ts b/website/config/inertia.ts index 1a1c084..c0e1798 100644 --- a/website/config/inertia.ts +++ b/website/config/inertia.ts @@ -6,7 +6,7 @@ import type { InferSharedProps } from '@adonisjs/inertia/types' export type AuthenticationData = | { state: 'disabled' } | { state: 'unauthenticated' } - | { state: 'authenticated'; user: Pick } + | { state: 'authenticated'; user: Pick } const inertiaConfig = defineConfig({ /** @@ -26,7 +26,8 @@ const inertiaConfig = defineConfig({ const user = auth.user if (!user) return { state: 'unauthenticated' } - return { state: 'authenticated', user: { email: user.email } } + await user.load('participantProfile') + return { state: 'authenticated', user: { email: user.email, role: user.role } } }, }, diff --git a/website/database/migrations/1739294506253_create_promoter_infos_table.ts b/website/database/migrations/1739294506253_create_promoter_profiles_table.ts similarity index 70% rename from website/database/migrations/1739294506253_create_promoter_infos_table.ts rename to website/database/migrations/1739294506253_create_promoter_profiles_table.ts index 731c9ba..eff020e 100644 --- a/website/database/migrations/1739294506253_create_promoter_infos_table.ts +++ b/website/database/migrations/1739294506253_create_promoter_profiles_table.ts @@ -1,14 +1,12 @@ import { BaseSchema } from '@adonisjs/lucid/schema' export default class extends BaseSchema { - protected tableName = 'promoter_infos' + protected tableName = 'promoter_profiles' async up() { this.schema.createTable(this.tableName, (table) => { table.increments('id') - - table.timestamp('created_at') - table.timestamp('updated_at') + table.timestamps({ defaultToNow: true }) }) } diff --git a/website/database/migrations/1739294511724_alter_users_table.ts b/website/database/migrations/1739294511724_alter_users_table.ts index 6e39d2c..83b2d8d 100644 --- a/website/database/migrations/1739294511724_alter_users_table.ts +++ b/website/database/migrations/1739294511724_alter_users_table.ts @@ -5,38 +5,28 @@ export default class extends BaseSchema { async up() { this.schema.alterTable(this.tableName, (table) => { - table.integer('promoter_info_id') - .unique() + table.integer('referring_promoter_id') .references('id') - .inTable('promoter_infos') - - table.integer('points').defaultTo(0) + .inTable('users') - table.integer('referred_by_promoter_id') - .unsigned() - .nullable() + table.integer('referrer_id') .references('id') .inTable('users') - .onDelete('SET NULL') - table.integer('referred_by_user_id') - .unsigned() - .nullable() + table.integer('promoter_profile_id') + .unique() .references('id') - .inTable('users') - .onDelete('SET NULL') + .inTable('promoter_profiles') }) } async down() { this.schema.alterTable(this.tableName, (table) => { - table.dropColumn('promoter_info_id') - - table.dropColumn('points') + table.dropColumn('promoter_profile_id') - table.dropColumn('referred_by_promoter_id') + table.dropColumn('referrer_id') - table.dropColumn('referred_by_user_id') + table.dropColumn('root_referrer_id') }) } } diff --git a/website/database/migrations/1739512417906_alter_participant_profiles_table.ts b/website/database/migrations/1739512417906_alter_participant_profiles_table.ts new file mode 100644 index 0000000..ae32113 --- /dev/null +++ b/website/database/migrations/1739512417906_alter_participant_profiles_table.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'participant_profiles' + + async up() { + this.schema.alterTable(this.tableName, (table) => { + table.string('purchased_ticket').nullable() + }) + } + + async down() { + this.schema.alterTable(this.tableName, (table) => { + table.dropColumn('purchased_ticket') + }) + } +} \ No newline at end of file diff --git a/website/database/seeders/2_account_seeder.dev.ts b/website/database/seeders/2_account_seeder.dev.ts new file mode 100644 index 0000000..dc0def1 --- /dev/null +++ b/website/database/seeders/2_account_seeder.dev.ts @@ -0,0 +1,49 @@ +import { logger } from '#lib/adonisjs/logger.js' +import ParticipantProfile from '#models/participant_profile' +// import PromoterProfile from '#models/promoter_profile' +import User from '#models/user' +import app from '@adonisjs/core/services/app' +import { BaseSeeder } from '@adonisjs/lucid/seeders' +import { DateTime } from 'luxon' + +export default class extends BaseSeeder { + async run() { + if (!app.inDev) { + logger().info('Not running in development environment, skipping...') + return + } + + const email = 'test@eneiconf.pt' + + const user = new User() + user.email = email + user.emailVerifiedAt = DateTime.now() + + await user.related('accounts').create({ + id: `credentials:${email}`, + password: 'password', + }) + + const profile = await ParticipantProfile.create({ + firstName: 'Jorge', + lastName: 'Costa', + dateOfBirth: DateTime.fromObject({ year: 2003, month: 5, day: 9 }), + phone: '+351917777777', + university: 'pt.up.fe', + course: 'M.EIC', + curricularYear: '2', + finishedAt: null, + municipality: 'Braga', + heardAboutENEI: 'friends', + shirtSize: 'M', + isVegetarian: false, + isVegan: false, + transports: ['car'], + attendedBeforeEditions: [] + }) + + // const profile = await PromoterProfile.create({}) + + await user.related('participantProfile').associate(profile) + } +} \ No newline at end of file diff --git a/website/inertia/components/common/navbar.tsx b/website/inertia/components/common/navbar.tsx index 00674fb..94223fd 100644 --- a/website/inertia/components/common/navbar.tsx +++ b/website/inertia/components/common/navbar.tsx @@ -85,7 +85,7 @@ export function Navbar({ className }: { className?: string }) { className={cn('w-full transition-colors duration-300', !onTop && 'bg-enei-blue', className)} > -
+
Ir para a página inicial - {auth.state === 'authenticated' ? ( - - ) : ( - auth.state === 'unauthenticated' && - )} +
+
+ + Referenciações + +
+
+ {auth.state === 'authenticated' ? ( + + ) : ( + auth.state === 'unauthenticated' && + )} +
+
diff --git a/website/inertia/components/common/page.tsx b/website/inertia/components/common/page.tsx index 74d466d..27e5317 100644 --- a/website/inertia/components/common/page.tsx +++ b/website/inertia/components/common/page.tsx @@ -3,6 +3,24 @@ import React from 'react' import { cn } from '~/lib/utils' import { Navbar } from './navbar' import {Toaster} from '~/components/ui/toaster' +import { useAuth } from '~/hooks/use_auth' +import { Notification } from '../notifications' + +function PromoterNotification() { + const auth = useAuth() + + if (auth.state !== 'authenticated' || auth.user.role !== 'promoter') + return null + + return ( + +
+

Conta de Promotor

+
+
+ ) +} + export default function Page({ title, className, @@ -16,8 +34,8 @@ export default function Page({
+ - {children}
) diff --git a/website/inertia/components/ui/label.tsx b/website/inertia/components/ui/label.tsx index 9a5caaf..da58f00 100644 --- a/website/inertia/components/ui/label.tsx +++ b/website/inertia/components/ui/label.tsx @@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '~/lib/utils' -const labelVariants = cva( +export const labelVariants = cva( 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' ) diff --git a/website/inertia/pages/referrals/page.tsx b/website/inertia/pages/referrals/page.tsx new file mode 100644 index 0000000..fb627cd --- /dev/null +++ b/website/inertia/pages/referrals/page.tsx @@ -0,0 +1,70 @@ +import ReferralsController from '#controllers/referrals_controller' +import { InferPageProps } from '@adonisjs/inertia/types' +import { Link } from '@tuyau/inertia/react' +import { CircleAlert } from 'lucide-react' +import Container from '~/components/common/containers' +import CardContainer from '~/components/common/containers/card' +import Page from '~/components/common/page' +import { buttonVariants } from '~/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card' +import { Input } from '~/components/ui/input' +import { Label } from '~/components/ui/label' +import { cn } from '~/lib/utils' + +export default function ReferralsPage({ + referralLink, +}: InferPageProps) { + const hasReferralLink = referralLink !== null + return ( + + + + + + +

Referenciações

+
+ + No ENEI, ao convidares pessoas para o evento, ganhas pontos para trocar por + recompensas! + +
+ +
+ + +

+ Partilha o teu link de referenciação com os teus amigos ou colegas. +
+ Por cada pessoa que se inscrever e comprar bilhete para o ENEI 2025 usando o teu + link, receberás pontos como recompensa. +

+
+ {!hasReferralLink && ( +
+ O link de referenciação só ficará + disponível quando{' '} + + comprares o teu bilhete para o ENEI 2025 + + . +
+ )} +
+
+
+
+
+ ) +} diff --git a/website/lib/adonisjs/cookies.ts b/website/lib/adonisjs/cookies.ts new file mode 100644 index 0000000..f7bb9d5 --- /dev/null +++ b/website/lib/adonisjs/cookies.ts @@ -0,0 +1,70 @@ +import { HttpContext } from '@adonisjs/core/http' +import type { CookieOptions } from '@adonisjs/core/types/http' + +type TypedCookieOptions = { kind: 'signed' } | { kind: 'encrypted' } + +const defaultOptions: TypedCookieOptions = { kind: 'signed' } + +class RawSignedCookie { + constructor(public name: string) {} + + get(ctx: HttpContext, defaultValue?: string) { + return ctx.request.cookie(this.name, defaultValue) + } + + set(ctx: HttpContext, value: any, options?: Partial) { + ctx.response.cookie(this.name, value, options) + } +} + +class RawEncryptedCookie { + constructor(public name: string) {} + + get(ctx: HttpContext, defaultValue?: string) { + return ctx.request.encryptedCookie(this.name, defaultValue) + } + + set(ctx: HttpContext, value: any, options?: Partial) { + ctx.response.encryptedCookie(this.name, value, options) + } +} + +export class TypedCookie { + #cookie: RawSignedCookie | RawEncryptedCookie + + constructor( + public name: string, + options?: Partial + ) { + const resolvedOptions = { ...defaultOptions, ...options } + + switch (resolvedOptions.kind) { + case 'signed': + this.#cookie = new RawSignedCookie(name) + break + case 'encrypted': + this.#cookie = new RawEncryptedCookie(name) + break + default: + throw new Error('Invalid cookie kind') + } + } + + get(ctx: HttpContext, defaultValue?: T) { + // The cast below is needed because @adonijs/core/http + // incorrectly enforces the type of the cookie value to be a string + // + // https://github.com/adonisjs/http-server/blob/d23061c14fda34ca082e731f16b161a8e1f4a2b3/src/request.ts#L894 + + const value = this.#cookie.get(ctx, defaultValue as unknown as string) + return value as T | null + } + + set(ctx: HttpContext, value: T, options?: Partial) { + this.#cookie.set(ctx, value, options) + } + + clear(ctx: HttpContext) { + ctx.response.clearCookie(this.name) + } +} diff --git a/website/package.json b/website/package.json index 78cb8c6..63ce22d 100644 --- a/website/package.json +++ b/website/package.json @@ -135,10 +135,6 @@ "cmdk": "^1.0.4", "command": "^0.0.5", "date-fns": "^4.1.0", - "hashids": "^2.3.0", - "framer-motion": "^11.15.0", - "ifthenpay": "^0.3.4", - "leaflet-geosearch": "^4.0.0", "edge.js": "^6.2.1", "embla-carousel": "^8.5.1", "embla-carousel-react": "^8.5.2", @@ -159,6 +155,7 @@ "recharts": "^2.15.0", "reflect-metadata": "^0.2.2", "sonner": "^1.7.2", + "sqids": "^0.3.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "use-resize-observer": "^9.1.0", diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index 0e11d63..d71faf1 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -212,24 +212,12 @@ importers: embla-carousel-react: specifier: ^8.5.2 version: 8.5.2(react@19.0.0) - framer-motion: - specifier: ^11.15.0 - version: 11.18.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - hashids: - specifier: ^2.3.0 - version: 2.3.0 - ifthenpay: - specifier: ^0.3.4 - version: 0.3.4 input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) jotai: specifier: ^2.11.1 version: 2.11.3(@types/react@19.0.8)(react@19.0.0) - leaflet-geosearch: - specifier: ^4.0.0 - version: 4.1.0 lucide-react: specifier: ^0.473.0 version: 0.473.0(react@19.0.0) @@ -275,6 +263,9 @@ importers: sonner: specifier: ^1.7.2 version: 1.7.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + sqids: + specifier: ^0.3.0 + version: 0.3.0 tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -1296,9 +1287,6 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@googlemaps/js-api-loader@1.16.8': - resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==} - '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -4399,20 +4387,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - framer-motion@11.18.2: - resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -4585,9 +4559,6 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - hashids@2.3.0: - resolution: {integrity: sha512-ljM73TE/avEhNnazxaj0Dw3BbEUuLC5yYCQ9RSkSUcT4ZSU6ZebdKCIBJ+xT/DnSYW36E9k82GH1Q6MydSIosQ==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -4717,9 +4688,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ifthenpay@0.3.4: - resolution: {integrity: sha512-CKafqjnTZpLrtqlPp4JVfUBK0zXLS4Da5U2pv8Dm3g1CCZfb1zCvttB0k8MUu0XtajPyBYtWLcDyqRhLqhQIPw==} - igniculus@1.5.0: resolution: {integrity: sha512-vhj2J/cSzNg2G5tcK4Z1KZdeYmQa5keoxFULUYAxctK/zHJb1oraO7noCqnJxKe1b2eZdiiaSL1IHPOFAI8UYQ==} engines: {node: '>=4.0.0'} @@ -5059,12 +5027,6 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} - leaflet-geosearch@4.1.0: - resolution: {integrity: sha512-HyqmRWInm5/B28PpAscruOFnYztamJji4OTsueNluvaHg/hYVMWpaReyWvfVUQGVn8U42+Mm01gBpOcRsqh60g==} - - leaflet@1.9.4: - resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -5331,12 +5293,6 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - motion-dom@11.18.1: - resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} - - motion-utils@11.18.1: - resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==} - ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -5603,9 +5559,6 @@ packages: package-manager-detector@0.2.8: resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} - pad-start@1.0.2: - resolution: {integrity: sha512-EBN8Ez1SVRcZT1XsIE4WkdnZ5coLoaChkIgAET6gIlaLhXqCz9upVk0DQWFtOYkrpTVvbEppRUnqhTiJrBdkfw==} - pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -6458,6 +6411,9 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sqids@0.3.0: + resolution: {integrity: sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==} + stacktracey@2.1.8: resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} @@ -8241,9 +8197,6 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@googlemaps/js-api-loader@1.16.8': - optional: true - '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@19.0.0))': dependencies: react-hook-form: 7.54.2(react@19.0.0) @@ -11458,15 +11411,6 @@ snapshots: fraction.js@4.3.7: {} - framer-motion@11.18.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - motion-dom: 11.18.1 - motion-utils: 11.18.1 - tslib: 2.8.1 - optionalDependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - fresh@0.5.2: {} fs-constants@1.0.0: {} @@ -11656,8 +11600,6 @@ snapshots: has-symbols@1.1.0: {} - hashids@2.3.0: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -11770,10 +11712,6 @@ snapshots: ieee754@1.2.1: {} - ifthenpay@0.3.4: - dependencies: - pad-start: 1.0.2 - igniculus@1.5.0: {} ignore@5.3.2: {} @@ -12040,14 +11978,6 @@ snapshots: leac@0.6.0: {} - leaflet-geosearch@4.1.0: - optionalDependencies: - '@googlemaps/js-api-loader': 1.16.8 - leaflet: 1.9.4 - - leaflet@1.9.4: - optional: true - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -12280,12 +12210,6 @@ snapshots: moment@2.30.1: {} - motion-dom@11.18.1: - dependencies: - motion-utils: 11.18.1 - - motion-utils@11.18.1: {} - ms@2.0.0: {} ms@2.1.2: {} @@ -12571,8 +12495,6 @@ snapshots: package-manager-detector@0.2.8: {} - pad-start@1.0.2: {} - pako@0.2.9: {} parent-module@1.0.1: @@ -13611,6 +13533,8 @@ snapshots: sprintf-js@1.1.3: {} + sqids@0.3.0: {} + stacktracey@2.1.8: dependencies: as-table: 1.0.55 diff --git a/website/start/kernel.ts b/website/start/kernel.ts index 1862afd..a18081a 100644 --- a/website/start/kernel.ts +++ b/website/start/kernel.ts @@ -42,7 +42,8 @@ router.use([ () => import('@adonisjs/auth/initialize_auth_middleware'), () => import('#middleware/auth/logout_if_authentication_disabled_middleware'), () => import('#middleware/log_user_middleware'), - () => import('#middleware/update_logger_storage_middleware') + () => import('#middleware/update_logger_storage_middleware'), + () => import('#middleware/link_to_user_middleware') ]) /** @@ -61,5 +62,6 @@ export const middleware = router.named({ verifySocialCallback: () => import('#middleware/auth/verify_social_callback_middleware'), guest: () => import('#middleware/auth/guest_middleware'), auth: () => import('#middleware/auth/auth_middleware'), + silentAuth: () => import('#middleware/auth/silent_auth_middleware'), finishRedirect: () => import('#middleware/finish_redirect_middleware'), }) diff --git a/website/start/routes.ts b/website/start/routes.ts index ed39ea0..19041cc 100644 --- a/website/start/routes.ts +++ b/website/start/routes.ts @@ -14,8 +14,7 @@ const AuthenticationController = () => import('#controllers/authentication_contr const OrdersController = () => import('#controllers/orders_controller') const TicketsController = () => import('#controllers/tickets_controller') const ProfilesController = () => import('#controllers/profiles_controller') - -router.use([() => import('#middleware/referral_middleware')]) +const ReferralsController = () => import('#controllers/referrals_controller') router.on('/').renderInertia('home').as('pages:home') @@ -152,3 +151,12 @@ router }) .use([middleware.auth(), middleware.verifiedEmail(), middleware.participant()]) .prefix('payment') + +// Referrals +router.get('/referrals', [ReferralsController, 'showReferralLink']) + .middleware(middleware.auth()) + .as('pages:referrals') + +router.route(`/r/:referralCode`, ['GET', 'POST'], [ReferralsController, 'link']) + .middleware([middleware.automaticSubmit(), middleware.silentAuth()]) + .as('actions:referrals.link') \ No newline at end of file From f16816343ff2c1e6501dd7a8995e86a84d70ff69 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 14 Feb 2025 11:49:57 +0000 Subject: [PATCH 22/22] chore: add postgreSQL config variables to env example --- website/.env.example | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/.env.example b/website/.env.example index 9e0c7cc..d6a9b7b 100644 --- a/website/.env.example +++ b/website/.env.example @@ -51,4 +51,10 @@ INERTIA_PUBLIC_TZ=Europe/Lisbon INERTIA_PUBLIC_EVENT_COUNTDOWN_DATE=2025-04-11 # Tuyau -INERTIA_PUBLIC_APP_URL=http://127.0.0.1:3333 \ No newline at end of file +INERTIA_PUBLIC_APP_URL=http://127.0.0.1:3333 + +DB_CONNECTION=postgres +POSTGRES_PORT=5432 +POSTGRES_USER=postgres +POSTGRES_PASSWORD= +POSTGRES_DB=postgres