Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: direct referral system #33

Draft
wants to merge 15 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions website/app/middleware/referral_middleware.ts
Original file line number Diff line number Diff line change
@@ -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()
}
}
13 changes: 13 additions & 0 deletions website/app/models/company_info.ts
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions website/app/models/company_representative_info.ts
Original file line number Diff line number Diff line change
@@ -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<typeof User>
}
27 changes: 27 additions & 0 deletions website/app/models/mixins/has_referral_link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 = <T extends NormalizeConstructor<typeof BaseModel>>(
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')
}
}
}
19 changes: 19 additions & 0 deletions website/app/models/participant_info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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<typeof User>

@column.dateTime({ autoCreate: true })
declare createdAt: DateTime

@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime

}
19 changes: 19 additions & 0 deletions website/app/models/promoter_info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DateTime } from 'luxon'
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 {
@column({ isPrimary: true })
declare id: number

@hasOne(() => User)
declare user: HasOne<typeof User>

@column.dateTime({ autoCreate: true })
declare createdAt: DateTime

@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime

}
45 changes: 42 additions & 3 deletions website/app/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { DateTime } from 'luxon'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
import { BaseModel, belongsTo, column, hasMany, hasOne } from '@adonisjs/lucid/orm'
import Account from './account.js'
import type { HasMany } 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'
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

Expand All @@ -22,7 +26,42 @@ export default class User extends BaseModel {
@hasMany(() => Account)
declare accounts: HasMany<typeof Account>

@column()
declare referredById: number | null

@belongsTo(() => User, {
foreignKey: 'id'
})
declare referredBy: BelongsTo<typeof User>

@column()
declare points: number

@column()
declare promoterInfoId: number | null

@belongsTo(() => PromoterInfo)
declare promoterInfo: BelongsTo<typeof PromoterInfo>

@column()
declare participantInfoId: number | null

@belongsTo(() => ParticipantInfo)
declare participantInfo: BelongsTo<typeof ParticipantInfo>

isStudentAssociation() {
return this.promoterInfoId !== null
}

isParticipant() {
return this.participantInfoId !== null
}

isEmailVerified() {
return this.emailVerifiedAt !== null
}

public getPromoterCode: () => number = () => {
return this.id;
}
}
55 changes: 55 additions & 0 deletions website/app/services/referral_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import User from '#models/user'
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 {
return ReferralService.hashIds.encode(id)
}

static decode(hashId: string): number {
return ReferralService.hashIds.decode(hashId)[0] as number
}

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()) {
await db.transaction(async (trx) => {
promoter.useTransaction(trx)
referredUser.useTransaction(trx)

promoter.points += this.POINTS_FOR_ASSOCIATION
// FIXME: Not associating
await referredUser.related('referredBy').associate(promoter)
})
} else if (promoter.isParticipant()) {
console.log("promoter is participant")
const referralAssociation = await User.find(promoter.referredById)

// 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 += this.POINTS_FOR_ASSOCIATION
await referredUser.related('referredBy').associate(referralAssociation)
}
promoter.points += this.POINTS_FOR_PARTICIPANT
})
}
}
}
21 changes: 21 additions & 0 deletions website/database/migrations/1739036994584_alter_users_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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_id')
.references('id')
.inTable('users')
})
}

async down() {
this.schema.alterTable(this.tableName, (table) => {
table.dropColumn('points')
table.dropColumn('referred_by_id')
})
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
25 changes: 25 additions & 0 deletions website/database/migrations/1739128445636_alter_users_table.ts
Original file line number Diff line number Diff line change
@@ -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')
})
}
}
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"date-fns": "^4.1.0",
"edge.js": "^6.2.0",
"embla-carousel-react": "^8.5.1",
"hashids": "^2.3.0",
"framer-motion": "^11.15.0",
"ifthenpay": "^0.3.4",
"input-otp": "^1.4.1",
Expand Down
Loading