Skip to content
Merged
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
26 changes: 26 additions & 0 deletions backend/src/project/project-limits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ForbiddenException, HttpStatus } from '@nestjs/common';
import { GraphQLError } from 'graphql';

export const PROJECT_DAILY_LIMIT = 3; // Maximum number of projects a user can create per day

export enum ProjectErrorCode {
DAILY_LIMIT_EXCEEDED = 'DAILY_LIMIT_EXCEEDED',
}

export class ProjectRateLimitException extends ForbiddenException {
constructor(limit: number) {
super(
`Daily project creation limit of ${limit} reached. Please try again tomorrow.`,
);
}

getGraphQLError(): GraphQLError {
return new GraphQLError(this.message, {
extensions: {
code: ProjectErrorCode.DAILY_LIMIT_EXCEEDED,
limit: PROJECT_DAILY_LIMIT,
status: HttpStatus.TOO_MANY_REQUESTS,
},
});
}
}
9 changes: 9 additions & 0 deletions backend/src/project/project.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ResolveField,
Parent,
ID,
Int,
} from '@nestjs/graphql';
import { ProjectService } from './project.service';
import { Project } from './project.model';
Expand Down Expand Up @@ -147,4 +148,12 @@ export class ProjectsResolver {
): Promise<Project[]> {
return this.projectService.fetchPublicProjects(input);
}

// In ProjectsResolver:
@Query(() => Int)
async getRemainingProjectLimit(
@GetUserIdFromToken() userId: string,
): Promise<number> {
return this.projectService.getRemainingProjectLimit(userId);
}
}
65 changes: 63 additions & 2 deletions backend/src/project/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ForbiddenException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Not, Repository } from 'typeorm';
import { Between, In, Not, Repository } from 'typeorm';
import { Project } from './project.model';
import { ProjectPackages } from './project-packages.model';
import {
Expand All @@ -26,6 +26,11 @@ import { BuilderContext } from 'src/build-system/context';
import { ChatService } from 'src/chat/chat.service';
import { Chat } from 'src/chat/chat.model';
import { v4 as uuidv4 } from 'uuid';
import {
PROJECT_DAILY_LIMIT,
ProjectRateLimitException,
} from './project-limits';

@Injectable()
export class ProjectService {
private readonly model: OpenAIModelProvider =
Expand Down Expand Up @@ -119,12 +124,41 @@ export class ProjectService {
}
}

/**
* Checks if a user has exceeded their daily project creation limit
* @param userId The user ID to check
* @returns A boolean indicating whether the user can create more projects today
*/
async canCreateProject(userId: string): Promise<boolean> {
const today = new Date();
today.setHours(0, 0, 0, 0); // Start of today

const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); // Start of tomorrow

// Count projects created by user today
const todayProjectCount = await this.projectsRepository.count({
where: {
userId: userId,
createdAt: Between(today, tomorrow),
},
});

return todayProjectCount < PROJECT_DAILY_LIMIT;
}

async createProject(
input: CreateProjectInput,
userId: string,
): Promise<Chat> {
try {
// First, handle project name generation if needed (this is the only sync operation we need)
//First check if user have reach the create project limit
const canCreate = await this.canCreateProject(userId);
if (!canCreate) {
throw new ProjectRateLimitException(PROJECT_DAILY_LIMIT);
}

// handle project name generation if needed (this is the only sync operation we need)
let projectName = input.projectName;
if (!projectName || projectName === '') {
this.logger.debug(
Expand Down Expand Up @@ -164,6 +198,10 @@ export class ProjectService {
// Return chat immediately so user can start interacting
return defaultChat;
} catch (error) {
if (error instanceof ProjectRateLimitException) {
throw error.getGraphQLError(); // Throw as a GraphQL error for the client
}

this.logger.error(
`Error in createProject: ${error.message}`,
error.stack,
Expand Down Expand Up @@ -602,4 +640,27 @@ export class ProjectService {

return [];
}

/**
* Gets the number of projects a user can still create today
* @param userId The user ID to check
* @returns The number of remaining projects the user can create today
*/
async getRemainingProjectLimit(userId: string): Promise<number> {
const today = new Date();
today.setHours(0, 0, 0, 0); // Start of today

const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); // Start of tomorrow

// Count projects created by this user today
const todayProjectCount = await this.projectsRepository.count({
where: {
userId: userId,
createdAt: Between(today, tomorrow),
},
});

return Math.max(0, PROJECT_DAILY_LIMIT - todayProjectCount);
}
}
Loading