-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
263 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { TvotesInsert } from '@database/schema'; | ||
import type { OmitType } from '@lib/types/utils'; | ||
import { Field, InputType } from '@nestjs/graphql'; | ||
import { IsSnowflake } from '@utils/graphql/validators/isSnowflake'; | ||
|
||
@InputType({ | ||
description: 'The input type for creating a vote' | ||
}) | ||
export class BotVoteCreateInput | ||
implements OmitType<TvotesInsert, 'expires' | 'userId'> | ||
{ | ||
/** | ||
* The bot ID of the vote. | ||
*/ | ||
@Field(() => String, { | ||
description: 'The bot ID of the vote' | ||
}) | ||
@IsSnowflake() | ||
public botId!: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import type { TvotesSelect } from '@database/schema'; | ||
import { Field, ID, ObjectType } from '@nestjs/graphql'; | ||
import { Paginated } from '@utils/graphql/pagination'; | ||
|
||
/** | ||
* Represents a vote object for a bot. | ||
*/ | ||
@ObjectType({ | ||
description: 'A vote object for a bot' | ||
}) | ||
export class BotVoteObject implements TvotesSelect { | ||
/** | ||
* The ID of the vote. | ||
*/ | ||
@Field(() => ID, { | ||
description: 'The ID of the vote' | ||
}) | ||
public id!: number; | ||
|
||
/** | ||
* The user ID of the vote. | ||
*/ | ||
@Field(() => String, { | ||
description: 'The user ID of the vote' | ||
}) | ||
public userId!: string; | ||
|
||
/** | ||
* The bot ID of the vote. | ||
*/ | ||
@Field(() => String, { | ||
description: 'The bot ID of the vote' | ||
}) | ||
public botId!: string; | ||
|
||
/** | ||
* The expiration date of the vote. | ||
*/ | ||
@Field(() => Number, { | ||
description: 'The expiration date of the vote' | ||
}) | ||
public expires!: number; | ||
} | ||
|
||
/** | ||
* Represents a paginated list of vote objects. | ||
*/ | ||
@ObjectType({ | ||
description: 'A paginated list of vote objects' | ||
}) | ||
export class BotVoteObjectConnection extends Paginated(BotVoteObject) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { User } from '@modules/auth/decorators/user.decorator'; | ||
import { JwtAuthGuard } from '@modules/auth/guards/jwt.guard'; | ||
import type { JwtPayload } from '@modules/auth/interfaces/payload.interface'; | ||
import { UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; | ||
import { Args, Mutation, Resolver } from '@nestjs/graphql'; | ||
import { ValidationTypes } from 'class-validator'; | ||
import { BotVoteCreateInput } from '../inputs/vote/create.input'; | ||
import { BotVoteObject } from '../objects/vote/vote.object'; | ||
import { BotVoteService } from '../services/vote.service'; | ||
|
||
/** | ||
* The resolver that contains mutations for bot votes. | ||
*/ | ||
@Resolver(() => BotVoteObject) | ||
@UsePipes(ValidationTypes, ValidationPipe) | ||
export class BotVoteResolver { | ||
/** | ||
* Creates an instance of `BotVoteResolver`. | ||
* @param voteService - The vote service. | ||
*/ | ||
public constructor(private readonly voteService: BotVoteService) {} | ||
|
||
/** | ||
* Creates a vote for a bot. | ||
* | ||
* @param input - The input data for creating the vote. | ||
* @param user - The authenticated user making the vote. | ||
* @returns A Promise that resolves to the created vote. | ||
*/ | ||
@Mutation(() => BotVoteObject) | ||
@UseGuards(JwtAuthGuard) | ||
public async createVote( | ||
@Args('input') input: BotVoteCreateInput, | ||
@User() user: JwtPayload | ||
) { | ||
return this.voteService.createVote(input.botId, user.id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { PaginatorService } from '@/services/paginator.service'; | ||
import { ErrorMessages } from '@constants/errors'; | ||
import { DATABASE } from '@constants/tokens'; | ||
import { votes } from '@database/schema'; | ||
import type { DrizzleService } from '@lib/types'; | ||
import { | ||
ForbiddenException, | ||
Inject, | ||
Injectable, | ||
type OnModuleInit | ||
} from '@nestjs/common'; | ||
import { ModuleRef } from '@nestjs/core'; | ||
import { hours } from '@nestjs/throttler'; | ||
import type { PaginationInput } from '@utils/graphql/pagination'; | ||
import { and, eq, gt } from 'drizzle-orm'; | ||
import { BotService } from './bot.service'; | ||
|
||
/** | ||
* Service class for managing votes for bots. | ||
*/ | ||
@Injectable() | ||
export class BotVoteService implements OnModuleInit { | ||
/** | ||
* The injected BotService instance. | ||
*/ | ||
private _botService!: BotService; | ||
|
||
/** | ||
* Constructs a new instance of the VoteService class. | ||
* @param _drizzleService - The injected DrizzleService instance. | ||
* @param _paginatorService - The injected PaginatorService instance. | ||
*/ | ||
public constructor( | ||
@Inject(DATABASE) private _drizzleService: DrizzleService, | ||
private _paginatorService: PaginatorService, | ||
private _moduleRef: ModuleRef | ||
) {} | ||
|
||
/** | ||
* Lifecycle hook that runs after the module has been initialized. | ||
*/ | ||
public onModuleInit() { | ||
this._botService = this._moduleRef.get(BotService, { strict: false }); | ||
} | ||
|
||
/** | ||
* Creates a new vote for a bot by a user. | ||
* @param botId - The ID of the bot. | ||
* @param userId - The ID of the user. | ||
* @returns The created vote. | ||
* @throws ForbiddenException if the user has already voted for the bot. | ||
*/ | ||
public async createVote(botId: string, userId: string) { | ||
// Check if the bot exists. | ||
await this._botService.getBot(botId); | ||
|
||
// Check if the user has already voted. | ||
if (await this.canVote(botId, userId)) { | ||
throw new ForbiddenException(ErrorMessages.VOTE_USER_ALREADY_VOTED); | ||
} | ||
|
||
// Create the vote. | ||
const [vote] = await this._drizzleService | ||
.insert(votes) | ||
.values({ | ||
botId, | ||
userId, | ||
expires: Date.now() + hours(12) | ||
}) | ||
.returning(); | ||
|
||
return vote; | ||
} | ||
|
||
/** | ||
* Retrieves the votes for a bot. | ||
* @param botId - The ID of the bot. | ||
* @param pagination - The pagination options. | ||
* @returns The paginated list of votes. | ||
*/ | ||
public async paginateVotes( | ||
botId: string, | ||
pagination: PaginationInput = {} | ||
) { | ||
return this._paginatorService.paginate< | ||
typeof votes._.config, | ||
typeof votes | ||
>({ | ||
schema: votes, | ||
where: eq(votes.botId, botId), | ||
pagination | ||
}); | ||
} | ||
|
||
/** | ||
* Checks if a user can vote for a bot. | ||
* @param botId - The ID of the bot. | ||
* @param userId - The ID of the user. | ||
* @returns A Promise that resolves to a boolean indicating if the user can vote. | ||
*/ | ||
public async canVote(botId: string, userId: string): Promise<boolean> { | ||
const userVotes = await this._drizzleService | ||
.select() | ||
.from(votes) | ||
.where( | ||
and( | ||
eq(votes.botId, botId), | ||
eq(votes.userId, userId), | ||
gt(votes.expires, Date.now()) | ||
) | ||
) | ||
.limit(1) | ||
.execute(); | ||
|
||
return !!userVotes.length; | ||
} | ||
} |