Implement token controll to access sessions

main
MiguelMLorente 2025-12-03 22:33:10 +01:00
parent 337b4f43e0
commit a95601bc34
9 changed files with 156 additions and 3 deletions

View File

@ -10,6 +10,8 @@ import { Purchase } from './dto/purchase';
import { SessionService } from './service/session.service';
import { SessionController } from './controller/session.controller';
import { Session } from './dto/session';
import { TokensController } from './controller/tokens.controller';
import { TokenService } from './service/token.service';
@Module({
imports: [
@ -26,7 +28,18 @@ import { Session } from './dto/session';
}),
TypeOrmModule.forFeature([User, Purchase, Session]),
],
controllers: [AccessController, BuyController, SessionController],
providers: [UserService, PurchaseService, StripeService, SessionService],
controllers: [
AccessController,
BuyController,
SessionController,
TokensController,
],
providers: [
UserService,
PurchaseService,
StripeService,
SessionService,
TokenService,
],
})
export class AppModule {}

View File

@ -4,6 +4,7 @@ import { UserService } from '../service/user.service';
import { PurchaseItem } from '../dto/purchase-item';
import { StripeService } from '../service/stripe.service';
import { PurchaseStatus } from 'src/dto/purchase-status';
import { TokenService } from 'src/service/token.service';
@Controller('/buy')
export class BuyController {
@ -16,6 +17,7 @@ export class BuyController {
constructor(
private readonly purchaseService: PurchaseService,
private readonly userService: UserService,
private readonly tokenService: TokenService,
private readonly stripeService: StripeService,
) {}
@ -54,6 +56,9 @@ export class BuyController {
? PurchaseStatus.COMPLETED
: PurchaseStatus.IN_PROGRESS;
this.purchaseService.recordPurchase(purchase);
if (purchase.status === PurchaseStatus.COMPLETED) {
this.tokenService.addTokens(user, purchase);
}
});
return { url: '/signup' };

View File

@ -0,0 +1,17 @@
import { Controller, Get, Query } from '@nestjs/common';
import { TokenService } from 'src/service/token.service';
import { UserService } from 'src/service/user.service';
@Controller('/tokens')
export class TokensController {
constructor(
private readonly userService: UserService,
private readonly tokenService: TokenService,
) {}
@Get()
public async getTokenCount(@Query('userId') userId: string) {
const user = await this.userService.getUserById(userId);
return await this.tokenService.getAvailableTokens(user);
}
}

View File

@ -0,0 +1,6 @@
export enum SessionStatus {
SCHEDULED = 'SCHEDULED',
OPEN = 'OPEN',
CLOSED = 'CLOSED',
CANCELLED = 'CANCELLED',
}

View File

@ -1,5 +1,6 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './user';
import { SessionStatus } from './session-status';
@Entity('SESSIONS')
export class Session {
@ -14,4 +15,7 @@ export class Session {
@Column()
date: Date;
@Column()
status: SessionStatus;
}

25
src/dto/token.ts Normal file
View File

@ -0,0 +1,25 @@
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { User } from './user';
@Entity('TOKENS')
export class Token {
@PrimaryColumn()
userId: string;
@OneToOne(() => User, (user) => user.token)
@JoinColumn({ name: 'userId' })
user: User;
@Column()
purchasedTokens: number;
@Column()
consumedTokens: number;
@Column()
lockedTokens: number;
get availableTokens() {
return this.purchasedTokens - this.consumedTokens - this.lockedTokens;
}
}

View File

@ -4,10 +4,12 @@ import {
JoinTable,
ManyToMany,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Purchase } from './purchase';
import { Session } from './session';
import { Token } from './token';
@Entity('USERS')
export class User {
@ -26,4 +28,7 @@ export class User {
@ManyToMany(() => Session, (session) => session.users)
@JoinTable()
joinedSessions: Session[];
@OneToOne(() => Token, (token) => token.user)
token: Token;
}

View File

@ -3,6 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Session } from 'src/dto/session';
import { User } from 'src/dto/user';
import { Repository } from 'typeorm';
import { TokenService } from './token.service';
import { SessionStatus } from 'src/dto/session-status';
@Injectable()
export class SessionService {
@ -10,6 +12,7 @@ export class SessionService {
constructor(
@InjectRepository(Session) private sessionRepo: Repository<Session>,
private tokenService: TokenService,
) {}
public createSession(date: Date, size: number) {
@ -17,10 +20,24 @@ export class SessionService {
users: [],
date,
size,
status: SessionStatus.OPEN,
});
this.sessionRepo.save(session);
}
public async closeSession(sessionId: string) {
const session = await this.sessionRepo.findOneOrFail({
where: { id: sessionId },
});
Promise.all(
session.users.map(
async (user) => await this.tokenService.consumeToken(user),
),
);
session.status = SessionStatus.CLOSED;
await this.sessionRepo.save(session);
}
public getAllSessions() {
return this.sessionRepo.find({
relations: { users: true },
@ -29,6 +46,7 @@ export class SessionService {
}
public async joinSession(user: User, sessionId: string) {
this.logger.debug(`User: ${user.id} tries to join session: ${sessionId}`);
const session = await this.sessionRepo.findOneOrFail({
where: {
id: sessionId,
@ -46,11 +64,13 @@ export class SessionService {
) {
return;
}
await this.tokenService.lockToken(user);
session.users.push(user);
await this.sessionRepo.save(session);
}
public async leaveSession(user: User, sessionId: string) {
this.logger.debug(`User: ${user.id} tries to leave session: ${sessionId}`);
const session = await this.sessionRepo.findOneOrFail({
where: {
id: sessionId,
@ -59,9 +79,10 @@ export class SessionService {
users: true,
},
});
if (!session) {
if (!session || !session.users.find((u) => u.id === user.id)) {
throw new NotFoundException();
}
this.tokenService.unlockToken(user);
session.users = session.users.filter((u) => u.id !== user.id);
await this.sessionRepo.save(session);
}

View File

@ -0,0 +1,57 @@
import {
Injectable,
InternalServerErrorException,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Purchase } from 'src/dto/purchase';
import { Token } from 'src/dto/token';
import { User } from 'src/dto/user';
import { Repository } from 'typeorm';
@Injectable()
export class TokenService {
private readonly logger = new Logger(TokenService.name);
constructor(@InjectRepository(Token) private tokenRepo: Repository<Token>) {}
public async addTokens(user: User, purchase: Purchase) {
const token = await this.tokenRepo.findOneOrFail({ where: { user: user } });
token.purchasedTokens += purchase.purchasedUnits;
await this.tokenRepo.save(token);
}
public async getAvailableTokens(user: User) {
const token = await this.tokenRepo.findOneOrFail({ where: { user: user } });
return token.availableTokens;
}
public async consumeToken(user: User) {
const token = await this.tokenRepo.findOneOrFail({ where: { user: user } });
if (token.lockedTokens <= 0) {
throw new InternalServerErrorException();
}
token.lockedTokens -= 1;
token.consumedTokens += 1;
await this.tokenRepo.save(token);
}
public async lockToken(user: User) {
const token = await this.tokenRepo.findOneOrFail({ where: { user: user } });
if (token.availableTokens <= 0) {
throw new NotFoundException();
}
token.lockedTokens += 1;
await this.tokenRepo.save(token);
}
public async unlockToken(user: User) {
const token = await this.tokenRepo.findOneOrFail({ where: { user: user } });
if (token.lockedTokens <= 0) {
throw new NotFoundException();
}
token.lockedTokens -= 1;
await this.tokenRepo.save(token);
}
}