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
2 changes: 1 addition & 1 deletion backend/src/project/project.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Project extends SystemBaseModel {

@Field(() => ID)
@Column()
userId: string;
userId: number;

@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
Expand Down
4 changes: 2 additions & 2 deletions backend/src/project/project.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class ProjectsResolver {

@Query(() => [Project])
async getUserProjects(
@GetUserIdFromToken() userId: string,
@GetUserIdFromToken() userId: number,
): Promise<Project[]> {
return this.projectsService.getProjectsByUser(userId);
}
Expand All @@ -36,7 +36,7 @@ export class ProjectsResolver {

@Mutation(() => Project)
async upsertProject(
@GetUserIdFromToken() userId: string,
@GetUserIdFromToken() userId: number,
@Args('upsertProjectInput') upsertProjectInput: UpsertProjectInput,
): Promise<Project> {
return this.projectsService.upsertProject(upsertProjectInput, userId);
Expand Down
4 changes: 2 additions & 2 deletions backend/src/project/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class ProjectService {
private projectPackagesRepository: Repository<ProjectPackages>,
) {}

async getProjectsByUser(userId: string): Promise<Project[]> {
async getProjectsByUser(userId: number): Promise<Project[]> {
const projects = await this.projectsRepository.find({
where: { userId: userId, isDeleted: false },
relations: ['projectPackages'],
Expand Down Expand Up @@ -57,7 +57,7 @@ export class ProjectService {

async upsertProject(
upsertProjectInput: UpsertProjectInput,
user_id: string,
user_id: number,
): Promise<Project> {
const { projectId, projectName, path, projectPackages } =
upsertProjectInput;
Expand Down
60 changes: 30 additions & 30 deletions backend/src/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ input LoginUserInput {
}

type Menu {
created_at: Date!
createdAt: Date!
id: ID!
is_active: Boolean!
is_deleted: Boolean!
last_updated: Date!
isActive: Boolean!
isDeleted: Boolean!
name: String!
path: String!
permission: String!
updatedAt: Date!
}

type Mutation {
Expand All @@ -58,35 +58,35 @@ type Mutation {
registerUser(input: RegisterUserInput!): User!
removePackageFromProject(packageId: String!, projectId: String!): Boolean!
updateProjectPath(newPath: String!, projectId: String!): Boolean!
upsertProject(upsertProjectInput: UpsertProjectInput!): Projects!
upsertProject(upsertProjectInput: UpsertProjectInput!): Project!
}

type ProjectPackages {
content: String!
created_at: Date!
type Project {
createdAt: Date!
id: ID!
is_active: Boolean!
is_deleted: Boolean!
last_updated: Date!
project_id: ID!
isActive: Boolean!
isDeleted: Boolean!
path: String
projectName: String!
projectPackages: [ProjectPackages!]
updatedAt: Date!
userId: ID!
}

type Projects {
created_at: Date!
type ProjectPackages {
content: String!
createdAt: Date!
id: ID!
is_active: Boolean!
is_deleted: Boolean!
last_updated: Date!
path: String!
projectPackages: [ProjectPackages!]
project_name: String!
user_id: ID!
isActive: Boolean!
isDeleted: Boolean!
project_id: ID!
updatedAt: Date!
}

type Query {
checkToken(input: CheckTokenInput!): Boolean!
getProjectDetails(projectId: String!): Projects!
getUserProjects: [Projects!]!
getProjectDetails(projectId: String!): Project!
getUserProjects: [Project!]!
logout: Boolean!
}

Expand All @@ -101,17 +101,17 @@ type Subscription {
}

input UpsertProjectInput {
project_id: ID
project_name: String!
project_packages: [String!]
projectId: ID
projectName: String!
projectPackages: [String!]
}

type User {
created_at: Date!
createdAt: Date!
email: String!
id: ID!
is_active: Boolean!
is_deleted: Boolean!
last_updated: Date!
isActive: Boolean!
isDeleted: Boolean!
updatedAt: Date!
username: String!
}
4 changes: 4 additions & 0 deletions frontend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
5 changes: 5 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"format": "prettier --write \"src/**/*.ts\""
},
"dependencies": {
"@apollo/client": "^3.11.8",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@hookform/resolvers": "^3.9.0",
Expand All @@ -32,6 +33,7 @@
"clsx": "^2.1.1",
"emoji-mart": "^5.6.0",
"framer-motion": "^11.5.6",
"graphql": "^16.9.0",
"langchain": "^0.3.2",
"lucide-react": "^0.445.0",
"next": "^14.2.13",
Expand All @@ -55,15 +57,18 @@
"zustand": "^5.0.0-rc.2"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^22.5.5",
"@types/react": "^18.3.8",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"eslint": "8.57.1",
"eslint-config-next": "14.2.13",
"jest": "^29.7.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.12",
"ts-jest": "^29.2.5",
"typescript": "^5.6.2"
}
}
132 changes: 132 additions & 0 deletions frontend/src/app/api/project/route.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need using nextjs' backend api, lets use graphql directly, create a graphql routing api

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { gql } from '@apollo/client';
import client from '../../../utils/apolloClient';

// Define the queries and mutations

// Fetch user projects
export const GET_USER_PROJECTS = gql`
query GetUserProjects {
getUserProjects {
id
projectName
path
projectPackages {
id
content
}
}
}
`;

export const getUserProjects = async (): Promise<any> => {
try {
const response = await client.query({
query: GET_USER_PROJECTS,
});
return response.data.getUserProjects;
} catch (error) {
console.error('Error fetching user projects:', error);
throw error;
}
};

// Fetch project details
export const GET_PROJECT_DETAILS = gql`
query GetProjectDetails($projectId: String!) {
getProjectDetails(projectId: $projectId) {
id
projectName
path
projectPackages {
id
content
}
}
}
`;

export const getProjectDetails = async (projectId: string): Promise<any> => {
try {
const response = await client.query({
query: GET_PROJECT_DETAILS,
variables: { projectId },
});
return response.data.getProjectDetails;
} catch (error) {
console.error('Error fetching project details:', error);
throw error;
}
};

// Upsert project (Create or Update)
export const UPSERT_PROJECT = gql`
mutation UpsertProject($upsertProjectInput: UpsertProjectInput!) {
upsertProject(upsertProjectInput: $upsertProjectInput) {
id
projectName
path
projectPackages {
id
content
}
}
}
`;

export const upsertProject = async (upsertProjectInput: any): Promise<any> => {
try {
const response = await client.mutate({
mutation: UPSERT_PROJECT,
variables: {
upsertProjectInput,
},
});
return response.data.upsertProject;
} catch (error) {
console.error('Error creating/updating project:', error);
throw error;
}
};

// Delete project
export const DELETE_PROJECT = gql`
mutation DeleteProject($projectId: String!) {
deleteProject(projectId: $projectId)
}
`;

export const deleteProject = async (projectId: string): Promise<boolean> => {
try {
const response = await client.mutate({
mutation: DELETE_PROJECT,
variables: { projectId },
});
return response.data.deleteProject;
} catch (error) {
console.error('Error deleting project:', error);
throw error;
}
};

// Remove package from project
export const REMOVE_PACKAGE_FROM_PROJECT = gql`
mutation RemovePackageFromProject($projectId: String!, $packageId: String!) {
removePackageFromProject(projectId: $projectId, packageId: $packageId)
}
`;

export const removePackageFromProject = async (
projectId: string,
packageId: string
): Promise<boolean> => {
try {
const response = await client.mutate({
mutation: REMOVE_PACKAGE_FROM_PROJECT,
variables: { projectId, packageId },
});
return response.data.removePackageFromProject;
} catch (error) {
console.error('Error removing package from project:', error);
throw error;
}
};
45 changes: 45 additions & 0 deletions frontend/src/test/projectApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
getUserProjects,
getProjectDetails,
deleteProject,
upsertProject,
removePackageFromProject,
} from '../app/api/project/route';

describe('Project API', () => {
let projectId: string;
let packageId: string;

it('should upsert a project', async () => {
const upsertProjectInput = {
projectName: 'Test Project',
projectPackages: ['Package 1', 'Package 2'],
};
const project = await upsertProject(upsertProjectInput);
expect(project).toHaveProperty('id');
projectId = project.id;
console.log('Project id is: ' + projectId);
packageId = project.projectPackages[0].id;
});

it('should get user projects', async () => {
const projects = await getUserProjects();
expect(Array.isArray(projects)).toBe(true);
expect(projects.length).toBeGreaterThan(0);
});

it('should get project details', async () => {
const projectDetails = await getProjectDetails(projectId);
expect(projectDetails).toHaveProperty('id', projectId);
});

it('should remove a package from project', async () => {
const removed = await removePackageFromProject(projectId, packageId);
expect(removed).toBe(true);
});

it('should delete a project', async () => {
const deleted = await deleteProject(projectId);
expect(deleted).toBe(true);
});
});
33 changes: 33 additions & 0 deletions frontend/src/utils/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
ApolloClient,
InMemoryCache,
HttpLink,
ApolloLink,
concat,
} from '@apollo/client';

const httpLink = new HttpLink({
// uri: process.env.NEXT_PUBLIC_API_BASE_URL,
uri: 'http://localhost:8080/graphql',
});

const authMiddleware = new ApolloLink((operation, forward) => {
// Get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// Use the setContext method to set the HTTP headers.
if (token) {
operation.setContext({
headers: {
Authorization: `Bearer ${token}`,
},
});
}
return forward(operation);
});

const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
cache: new InMemoryCache(),
});

export default client;
Loading