diff --git a/backend/src/build-system/__tests__/mock/MockBuilderContext.ts b/backend/src/build-system/__tests__/mock/MockBuilderContext.ts index 9abd5c29..3cebd04c 100644 --- a/backend/src/build-system/__tests__/mock/MockBuilderContext.ts +++ b/backend/src/build-system/__tests__/mock/MockBuilderContext.ts @@ -85,8 +85,8 @@ export class MockBuilderContext extends BuilderContext { return this.virtualDirectory.parseJsonStructure(jsonContent); } - execute(): Promise { - return Promise.resolve(); // Mock a resolved promise for execution + execute(): Promise { + return Promise.resolve(''); // Mock a resolved promise for execution } getNodeData(handler: any): any { diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 5e9107e8..5fa56427 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -97,7 +97,7 @@ export class BuilderContext { this.handlerManager = BuildHandlerManager.getInstance(); this.model = OpenAIModelProvider.getInstance(); this.monitor = BuildMonitor.getInstance(); - this.logger = new Logger(`builder-context-${id}`); + this.logger = new Logger(`builder-context-${id ?? sequence.id}`); this.virtualDirectory = new VirtualDirectory(); // Initialize global context with default project values @@ -105,12 +105,13 @@ export class BuilderContext { this.globalContext.set('description', sequence.description || ''); this.globalContext.set('platform', 'web'); // Default platform is 'web' this.globalContext.set('databaseType', sequence.databaseType || 'SQLite'); - this.globalContext.set( - 'projectUUID', - new Date().toISOString().slice(0, 18).replaceAll(/:/g, '-') + + + const projectUUIDPath = + new Date().toISOString().slice(0, 18).replaceAll(/:/g, '-') + '-' + - uuidv4(), - ); + uuidv4(); + this.globalContext.set('projectUUID', projectUUIDPath); + if (process.env.DEBUG) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); @@ -313,7 +314,7 @@ export class BuilderContext { * @returns A promise that resolves when the entire build sequence is complete. */ - async execute(): Promise { + async execute(): Promise { try { const nodes = this.sequence.nodes; let currentIndex = 0; @@ -370,6 +371,7 @@ export class BuilderContext { await Promise.all(Array.from(runningPromises)); await new Promise((resolve) => setTimeout(resolve, this.POLL_INTERVAL)); } + return this.getGlobalContext('projectUUID'); } catch (error) { this.writeLog('execution-error.json', { error: error.message, diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index 0c13a4f2..69abc990 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -30,6 +30,11 @@ export interface BuildSequence { description?: string; databaseType?: string; nodes: BuildNode[]; + packages: BuildProjectPackage[]; +} +export interface BuildProjectPackage { + name: string; + version: string; } /** diff --git a/backend/src/common/model-provider/openai-model-provider.ts b/backend/src/common/model-provider/openai-model-provider.ts index 35db77ae..abcb0707 100644 --- a/backend/src/common/model-provider/openai-model-provider.ts +++ b/backend/src/common/model-provider/openai-model-provider.ts @@ -16,6 +16,7 @@ export class OpenAIModelProvider implements IModelProvider { private readonly logger = new Logger('OpenAIModelProvider'); private queues: Map = new Map(); private configLoader: ConfigLoader; + private defaultModel: string; private constructor() { this.openai = new OpenAI({ @@ -35,6 +36,9 @@ export class OpenAIModelProvider implements IModelProvider { const chatModels = this.configLoader.getAllChatModelConfigs(); for (const model of chatModels) { + if (model.default) { + this.defaultModel = model.model; + } if (!model.endpoint || !model.token) continue; const key = this.getQueueKey(model); @@ -87,7 +91,7 @@ export class OpenAIModelProvider implements IModelProvider { async chatSync(input: ChatInput): Promise { try { - const queue = this.getQueueForModel(input.model); + const queue = this.getQueueForModel(input.model ?? this.defaultModel); const completion = await queue.add(async () => { const result = await this.openai.chat.completions.create({ messages: input.messages, diff --git a/backend/src/common/model-provider/types.ts b/backend/src/common/model-provider/types.ts index e8e31222..60c2388b 100644 --- a/backend/src/common/model-provider/types.ts +++ b/backend/src/common/model-provider/types.ts @@ -20,7 +20,8 @@ export interface MessageInterface { } export interface ChatInput { - model: string; + // if model not provided, default model will be used + model?: string; messages: MessageInterface[]; } diff --git a/backend/src/guard/project.guard.ts b/backend/src/guard/project.guard.ts index 314388f3..a6dac748 100644 --- a/backend/src/guard/project.guard.ts +++ b/backend/src/guard/project.guard.ts @@ -3,6 +3,7 @@ import { CanActivate, ExecutionContext, UnauthorizedException, + Logger, } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { JwtService } from '@nestjs/jwt'; @@ -11,6 +12,8 @@ import { ProjectService } from '../project/project.service'; @Injectable() export class ProjectGuard implements CanActivate { + private readonly logger = new Logger('ProjectGuard'); + constructor( private readonly projectsService: ProjectService, private readonly jwtService: JwtService, @@ -19,38 +22,78 @@ export class ProjectGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const gqlContext = GqlExecutionContext.create(context); const request = gqlContext.getContext().req; + const args = gqlContext.getArgs(); + + // Verify and decode JWT token + const user = await this.validateToken(request); + + // Extract project identifier from arguments + const projectIdentifier = this.extractProjectIdentifier(args); + + if (!projectIdentifier) { + this.logger.debug('No project identifier found in request'); + return true; // Skip check if no project identifier is found + } - // Extract the authorization header + // Validate project ownership + await this.validateProjectOwnership(projectIdentifier, user.userId); + + // Store user in request context for later use + request.user = user; + return true; + } + + private async validateToken(request: any): Promise { const authHeader = request.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new UnauthorizedException('Authorization token is missing'); } - // Decode the token to get user information const token = authHeader.split(' ')[1]; - let user: any; try { - user = this.jwtService.verify(token); + return this.jwtService.verify(token); } catch (error) { + this.logger.error(`Token validation failed: ${error.message}`); throw new UnauthorizedException('Invalid token'); } + } - // Extract projectId from the request arguments - const args = gqlContext.getArgs(); - const { projectId } = args; + private extractProjectIdentifier(args: any): string | undefined { + // Handle different input formats + if (args.projectId) return args.projectId; + if (args.input?.projectId) return args.input.projectId; + if (args.isValidProject?.projectId) return args.isValidProject.projectId; + if (args.projectPath) return args.projectPath; + if (args.input?.projectPath) return args.input.projectPath; + if (args.isValidProject?.projectPath) + return args.isValidProject.projectPath; - // Fetch the project and check if the userId matches the project's userId - const project = await this.projectsService.getProjectById(projectId); - if (!project) { + return undefined; + } + + private async validateProjectOwnership( + projectIdentifier: string, + userId: number, + ): Promise { + let project; + try { + project = await this.projectsService.getProjectById(projectIdentifier); + } catch (error) { + this.logger.error(`Failed to fetch project: ${error.message}`); throw new UnauthorizedException('Project not found'); } - //To do: In the feature when we need allow teams add check here - - if (project.userId !== user.userId) { - throw new UnauthorizedException('User is not the owner of the project'); + if (!project) { + throw new UnauthorizedException('Project not found'); } - return true; + if (project.userId !== userId) { + this.logger.warn( + `Unauthorized access attempt: User ${userId} tried to access project ${projectIdentifier}`, + ); + throw new UnauthorizedException( + 'User is not authorized to access this project', + ); + } } } diff --git a/backend/src/project/build-system-utils.ts b/backend/src/project/build-system-utils.ts new file mode 100644 index 00000000..6ff1bf57 --- /dev/null +++ b/backend/src/project/build-system-utils.ts @@ -0,0 +1,123 @@ +import { BackendCodeHandler } from 'src/build-system/handlers/backend/code-generate'; +import { BackendFileReviewHandler } from 'src/build-system/handlers/backend/file-review/file-review'; +import { BackendRequirementHandler } from 'src/build-system/handlers/backend/requirements-document'; +import { DBRequirementHandler } from 'src/build-system/handlers/database/requirements-document'; +import { DBSchemaHandler } from 'src/build-system/handlers/database/schemas/schemas'; +import { FileFAHandler } from 'src/build-system/handlers/file-manager/file-arch'; +import { FileStructureHandler } from 'src/build-system/handlers/file-manager/file-structure'; +import { FrontendCodeHandler } from 'src/build-system/handlers/frontend-code-generate'; +import { PRDHandler } from 'src/build-system/handlers/product-manager/product-requirements-document/prd'; +import { ProjectInitHandler } from 'src/build-system/handlers/project-init'; +import { UXDMDHandler } from 'src/build-system/handlers/ux/datamap'; +import { UXSMDHandler } from 'src/build-system/handlers/ux/sitemap-document'; +import { UXSMSHandler } from 'src/build-system/handlers/ux/sitemap-structure'; +import { UXSMSPageByPageHandler } from 'src/build-system/handlers/ux/sitemap-structure/sms-page'; +import { BuildSequence } from 'src/build-system/types'; +import { v4 as uuidv4 } from 'uuid'; +import { CreateProjectInput } from './dto/project.input'; + +export function buildProjectSequenceByProject( + input: CreateProjectInput, +): BuildSequence { + const sequence: BuildSequence = { + id: uuidv4(), + version: '1.0.0', + name: input.projectName, + description: input.description, + databaseType: input.databaseType, + packages: input.packages, + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + }, + { + handler: PRDHandler, + name: 'Project Requirements Document Node', + }, + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, + { + handler: UXSMSHandler, + name: 'UX Sitemap Structure Node', + }, + { + handler: UXDMDHandler, + name: 'UX DataMap Document Node', + }, + { + handler: DBRequirementHandler, + name: 'Database Requirements Node', + }, + { + handler: FileStructureHandler, + name: 'File Structure Generation', + options: { + projectPart: 'frontend', + }, + }, + { + handler: UXSMSPageByPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', + }, + { + handler: DBSchemaHandler, + name: 'Database Schemas Node', + }, + { + handler: FileFAHandler, + name: 'File Arch', + }, + { + handler: BackendRequirementHandler, + name: 'Backend Requirements Node', + }, + { + handler: BackendCodeHandler, + name: 'Backend Code Generator Node', + }, + { + handler: BackendFileReviewHandler, + name: 'Backend File Review Node', + }, + { + handler: FrontendCodeHandler, + name: 'Frontend Code Generator Node', + }, + ], + }; + return sequence; +} + +/** + * Generates a project name prompt based on the provided description. + */ +export function generateProjectNamePrompt(description: string): string { + return `You are a project name generator. Based on the following project description, generate a concise, memorable, and meaningful project name. + +Input Description: ${description} + +Requirements for the project name: +1. Must be 1-3 words maximum +2. Should be clear and professional +3. Avoid generic terms like "project" or "system" +4. Use camelCase or kebab-case format +5. Should reflect the core functionality or purpose +6. Must be unique and memorable +7. Should be easy to pronounce +8. Avoid acronyms unless they're very intuitive + +Please respond ONLY with the project name, without any explanation or additional text. + +Example inputs and outputs: +Description: "A task management system with real-time collaboration features" +Output: taskFlow + +Description: "An AI-powered document analysis and extraction system" +Output: docMind + +Description: "A microservice-based e-commerce platform with advanced inventory management" +Output: tradeCore`; +} diff --git a/backend/src/project/dto/project.input.ts b/backend/src/project/dto/project.input.ts index 572a4e21..9f39fa3d 100644 --- a/backend/src/project/dto/project.input.ts +++ b/backend/src/project/dto/project.input.ts @@ -1,6 +1,9 @@ // DTOs for Project APIs import { InputType, Field, ID } from '@nestjs/graphql'; +/** + * @deprecated We don't need project upsert + */ @InputType() export class UpsertProjectInput { @Field() @@ -14,3 +17,36 @@ export class UpsertProjectInput { @Field(() => [String], { nullable: true }) projectPackages: string[]; } + +@InputType() +export class CreateProjectInput { + @Field(() => String, { nullable: true }) + projectName?: string; + + @Field() + description: string; + + @Field(() => [ProjectPackage]) + packages: ProjectPackage[]; + + @Field(() => String, { nullable: true }) + databaseType?: string; +} + +@InputType() +export class ProjectPackage { + @Field() + name: string; + + @Field() + version: string; +} + +@InputType() +export class IsValidProjectInput { + @Field(() => ID) + projectId: string; + + @Field(() => String, { nullable: true }) + projectPath: string; +} diff --git a/backend/src/project/project-packages.model.ts b/backend/src/project/project-packages.model.ts index cfad6535..934554aa 100644 --- a/backend/src/project/project-packages.model.ts +++ b/backend/src/project/project-packages.model.ts @@ -1,12 +1,6 @@ import { ObjectType, Field, ID } from '@nestjs/graphql'; import { SystemBaseModel } from 'src/system-base-model/system-base.model'; -import { - Entity, - PrimaryGeneratedColumn, - Column, - ManyToOne, - JoinColumn, -} from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; import { Project } from './project.model'; @Entity() @@ -16,15 +10,14 @@ export class ProjectPackages extends SystemBaseModel { @PrimaryGeneratedColumn() id: string; - @Field(() => ID) - @Column() - project_id: string; - @Field() @Column('text') content: string; - @ManyToOne(() => Project, (project) => project.projectPackages) - @JoinColumn({ name: 'project_id' }) - project: Project; + @Field() + @Column() + version: string; + + @ManyToMany(() => Project, (project) => project.projectPackages) + projects: Project[]; } diff --git a/backend/src/project/project.model.ts b/backend/src/project/project.model.ts index f3f59791..599a675d 100644 --- a/backend/src/project/project.model.ts +++ b/backend/src/project/project.model.ts @@ -5,8 +5,9 @@ import { PrimaryGeneratedColumn, Column, ManyToOne, - OneToMany, JoinColumn, + ManyToMany, + JoinTable, } from 'typeorm'; import { User } from 'src/user/user.model'; import { ProjectPackages } from './project-packages.model'; @@ -24,7 +25,7 @@ export class Project extends SystemBaseModel { @Field() @Column() - path: string; + projectPath: string; @Field(() => ID) @Column() @@ -35,10 +36,20 @@ export class Project extends SystemBaseModel { user: User; @Field(() => [ProjectPackages], { nullable: true }) - @OneToMany( + @ManyToMany( () => ProjectPackages, - (projectPackage) => projectPackage.project, - { cascade: true }, + (projectPackage) => projectPackage.projects, ) + @JoinTable({ + name: 'project_packages_mapping', + joinColumn: { + name: 'project_id', + referencedColumnName: 'id', + }, + inverseJoinColumn: { + name: 'package_id', + referencedColumnName: 'id', + }, + }) projectPackages: ProjectPackages[]; } diff --git a/backend/src/project/project.resolver.ts b/backend/src/project/project.resolver.ts index 44005829..d9f4b7f6 100644 --- a/backend/src/project/project.resolver.ts +++ b/backend/src/project/project.resolver.ts @@ -2,7 +2,7 @@ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { ProjectService } from './project.service'; import { Project } from './project.model'; -import { UpsertProjectInput } from './dto/project.input'; +import { CreateProjectInput, IsValidProjectInput } from './dto/project.input'; import { UseGuards } from '@nestjs/common'; import { ProjectGuard } from '../guard/project.guard'; import { GetUserIdFromToken } from '../decorator/get-auth-token.decorator'; @@ -28,11 +28,11 @@ export class ProjectsResolver { } @Mutation(() => Project) - async upsertProject( + async createPorject( @GetUserIdFromToken() userId: number, - @Args('upsertProjectInput') upsertProjectInput: UpsertProjectInput, + @Args('createProjectInput') createProjectInput: CreateProjectInput, ): Promise { - return this.projectsService.upsertProject(upsertProjectInput, userId); + return this.projectsService.createProject(createProjectInput, userId); } @Mutation(() => Boolean) @@ -41,21 +41,11 @@ export class ProjectsResolver { return this.projectsService.deleteProject(projectId); } - @Mutation(() => Boolean) - @UseGuards(ProjectGuard) - async updateProjectPath( - @Args('projectId') projectId: string, - @Args('newPath') newPath: string, - ): Promise { - return this.projectsService.updateProjectPath(projectId, newPath); - } - - @Mutation(() => Boolean) - @UseGuards(ProjectGuard) - async removePackageFromProject( - @Args('projectId') projectId: string, - @Args('packageId') packageId: string, + @Query(() => Boolean) + async isValidateProject( + @GetUserIdFromToken() userId: number, + @Args('isValidProject') input: IsValidProjectInput, ): Promise { - return this.projectsService.removePackageFromProject(projectId, packageId); + return this.projectsService.isValidProject(userId, input); } } diff --git a/backend/src/project/project.service.ts b/backend/src/project/project.service.ts index a1b4c087..cc70180e 100644 --- a/backend/src/project/project.service.ts +++ b/backend/src/project/project.service.ts @@ -3,15 +3,30 @@ import { Injectable, NotFoundException, InternalServerErrorException, + Logger, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { Project } from './project.model'; import { ProjectPackages } from './project-packages.model'; -import { UpsertProjectInput } from './dto/project.input'; +import { + CreateProjectInput, + IsValidProjectInput, + ProjectPackage, +} from './dto/project.input'; +import { + buildProjectSequenceByProject, + generateProjectNamePrompt, +} from './build-system-utils'; +import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; +import { MessageRole } from 'src/chat/message.model'; +import { BuilderContext } from 'src/build-system/context'; @Injectable() export class ProjectService { + private readonly model: OpenAIModelProvider = + OpenAIModelProvider.getInstance(); + private readonly logger = new Logger('ProjectService'); constructor( @InjectRepository(Project) private projectsRepository: Repository, @@ -55,60 +70,106 @@ export class ProjectService { return project; } - async upsertProject( - upsertProjectInput: UpsertProjectInput, - user_id: number, + // staring build the project + async createProject( + input: CreateProjectInput, + userId: number, ): Promise { - const { projectId, projectName, path, projectPackages } = - upsertProjectInput; - - let project; - if (projectId) { - // only extract the project match the user id - project = await this.projectsRepository.findOne({ - where: { id: projectId, isDeleted: false, userId: user_id }, + if (input.projectName === '') { + this.logger.debug( + 'Project name not exist in input, generating project name', + ); + const nameGenerationPrompt = await generateProjectNamePrompt( + input.description, + ); + const response = await this.model.chatSync({ + messages: [ + { + role: MessageRole.System, + content: + 'You are a specialized project name generator. Respond only with the generated name.', + }, + { + role: MessageRole.User, + content: nameGenerationPrompt, + }, + ], }); + input.projectName = response; + this.logger.debug(`Generated project name: ${input.projectName}`); } - if (project) { - // Update existing project - if (projectName) project.project_name = projectName; - if (path) project.path = path; - } else { - // Create a new project if it does not exist - project = this.projectsRepository.create({ - projectName: projectName, - path, - userId: user_id, - }); - project = await this.projectsRepository.save(project); - } + // Build project sequence and get project path + const sequence = buildProjectSequenceByProject(input); + const context = new BuilderContext(sequence, sequence.id); + const projectPath = await context.execute(); - // Add new project packages to existing ones - if (projectPackages && projectPackages.length > 0) { - const newPackages = projectPackages.map((content) => { - return this.projectPackagesRepository.create({ - project: project, - content: content, - }); - }); - await this.projectPackagesRepository.save(newPackages); + // Create new project entity + const project = new Project(); + project.projectName = input.projectName; + project.projectPath = projectPath; + project.userId = userId; + + // Transform input packages to ProjectPackages entities + const projectPackages = await this.transformInputToProjectPackages( + input.packages, + ); + project.projectPackages = projectPackages; + + try { + return await this.projectsRepository.save(project); + } catch (error) { + this.logger.error('Error creating project:', error); + throw new InternalServerErrorException('Error creating the project.'); } + } - // Return the updated or created project with all packages - return await this.projectsRepository - .findOne({ - where: { id: project.id, isDeleted: false }, - relations: ['projectPackages'], - }) - .then((project) => { - if (project && project.projectPackages) { - project.projectPackages = project.projectPackages.filter( - (pkg) => !pkg.isDeleted, - ); - } - return project; + private async transformInputToProjectPackages( + inputPackages: ProjectPackage[], + ): Promise { + try { + // Find existing packages in database + const packageNames = inputPackages.map((pkg) => pkg.name); + const existingPackages = await this.projectPackagesRepository.find({ + where: { + content: In(packageNames), + }, }); + + // Create map of existing packages for quick lookup + const existingPackagesMap = new Map( + existingPackages.map((pkg) => [pkg.content, pkg]), + ); + + // Transform each input package + const transformedPackages = await Promise.all( + inputPackages.map(async (inputPkg) => { + // Check if package already exists + const existingPackage = existingPackagesMap.get(inputPkg.name); + if (existingPackage) { + // Update version if needed + if (existingPackage.version !== inputPkg.version) { + existingPackage.version = inputPkg.version; + return await this.projectPackagesRepository.save(existingPackage); + } + return existingPackage; + } + + // Create new package if it doesn't exist + const newPackage = new ProjectPackages(); + newPackage.content = inputPkg.name; + newPackage.version = inputPkg.version; + return await this.projectPackagesRepository.save(newPackage); + }), + ); + + return transformedPackages; + } catch (error) { + this.logger.error('Error transforming packages:', error); + throw new InternalServerErrorException( + 'Error processing project packages.', + ); + } } async deleteProject(projectId: string): Promise { @@ -141,41 +202,37 @@ export class ProjectService { } } - async removePackageFromProject( - projectId: string, - packageId: string, + async isValidProject( + userId: number, + input: IsValidProjectInput, ): Promise { - const packageToRemove = await this.projectPackagesRepository.findOne({ - where: { id: packageId, project: { id: projectId } }, - }); - if (!packageToRemove) { - throw new NotFoundException( - `Package with ID ${packageId} not found for Project ID ${projectId}`, - ); - } + try { + const project = await this.projectsRepository.findOne({ + where: { + id: input.projectId, + projectPath: input.projectPath, + isDeleted: false, + }, + }); - packageToRemove.isActive = false; - packageToRemove.isDeleted = true; - await this.projectPackagesRepository.save(packageToRemove); + if (!project) { + this.logger.debug( + `Project not found with id: ${input.projectId}, path: ${input.projectPath}`, + ); + return false; + } - return true; - } + if (project.userId !== userId) { + this.logger.debug( + `User ${userId} is not owner of project ${input.projectId}`, + ); + return false; + } - async updateProjectPath( - projectId: string, - newPath: string, - ): Promise { - const project = await this.projectsRepository.findOne({ - where: { id: projectId, isDeleted: false }, - relations: ['projectPackages'], - }); - if (!project) { - throw new NotFoundException(`Project with ID ${projectId} not found.`); + return true; + } catch (error) { + this.logger.error(`Error validating project: ${error.message}`); + return false; } - - const result = await this.projectsRepository.update(projectId, { - path: newPath, - }); - return result.affected > 0; } } diff --git a/frontend/src/graphql/schema.gql b/frontend/src/graphql/schema.gql index 01a13ce5..099a98d3 100644 --- a/frontend/src/graphql/schema.gql +++ b/frontend/src/graphql/schema.gql @@ -43,9 +43,21 @@ input CheckTokenInput { token: String! } +input CreateProjectInput { + databaseType: String + description: String! + packages: [ProjectPackage!]! + projectName: String +} + """Date custom scalar type""" scalar Date +input IsValidProjectInput { + projectId: ID! + projectPath: String +} + type LoginResponse { accessToken: String! } @@ -80,15 +92,13 @@ type Message { type Mutation { clearChatHistory(chatId: String!): Boolean! createChat(newChatInput: NewChatInput!): Chat! + createPorject(createProjectInput: CreateProjectInput!): Project! deleteChat(chatId: String!): Boolean! deleteProject(projectId: String!): Boolean! login(input: LoginUserInput!): LoginResponse! registerUser(input: RegisterUserInput!): User! - removePackageFromProject(packageId: String!, projectId: String!): Boolean! triggerChatStream(input: ChatInputType!): Boolean! updateChatTitle(updateChatTitleInput: UpdateChatTitleInput!): Chat - updateProjectPath(newPath: String!, projectId: String!): Boolean! - upsertProject(upsertProjectInput: UpsertProjectInput!): Project! } input NewChatInput { @@ -100,21 +110,26 @@ type Project { id: ID! isActive: Boolean! isDeleted: Boolean! - path: String! projectName: String! projectPackages: [ProjectPackages!] + projectPath: String! updatedAt: Date! userId: ID! } +input ProjectPackage { + name: String! + version: String! +} + type ProjectPackages { content: String! createdAt: Date! id: ID! isActive: Boolean! isDeleted: Boolean! - project_id: ID! updatedAt: Date! + version: String! } type Query { @@ -126,6 +141,7 @@ type Query { getProjectDetails(projectId: String!): Project! getUserChats: [Chat!] getUserProjects: [Project!]! + isValidateProject(isValidProject: IsValidProjectInput!): Boolean! logout: Boolean! me: User! } @@ -156,12 +172,6 @@ input UpdateChatTitleInput { title: String } -input UpsertProjectInput { - projectId: ID - projectName: String! - projectPackages: [String!] -} - type User { chats: [Chat!]! createdAt: Date!