Skip to content

Commit 08ceb20

Browse files
Add project frontend api with test and fix backend project api (#20)
This pr add project frontend api with test and fix backend project api (user id should be number) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 161306c commit 08ceb20

File tree

10 files changed

+554
-72
lines changed

10 files changed

+554
-72
lines changed

backend/src/project/project.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class Project extends SystemBaseModel {
2828

2929
@Field(() => ID)
3030
@Column()
31-
userId: string;
31+
userId: number;
3232

3333
@ManyToOne(() => User)
3434
@JoinColumn({ name: 'user_id' })

backend/src/project/project.resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class ProjectsResolver {
2020

2121
@Query(() => [Project])
2222
async getUserProjects(
23-
@GetUserIdFromToken() userId: string,
23+
@GetUserIdFromToken() userId: number,
2424
): Promise<Project[]> {
2525
return this.projectsService.getProjectsByUser(userId);
2626
}
@@ -36,7 +36,7 @@ export class ProjectsResolver {
3636

3737
@Mutation(() => Project)
3838
async upsertProject(
39-
@GetUserIdFromToken() userId: string,
39+
@GetUserIdFromToken() userId: number,
4040
@Args('upsertProjectInput') upsertProjectInput: UpsertProjectInput,
4141
): Promise<Project> {
4242
return this.projectsService.upsertProject(upsertProjectInput, userId);

backend/src/project/project.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ProjectService {
1919
private projectPackagesRepository: Repository<ProjectPackages>,
2020
) {}
2121

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

5858
async upsertProject(
5959
upsertProjectInput: UpsertProjectInput,
60-
user_id: string,
60+
user_id: number,
6161
): Promise<Project> {
6262
const { projectId, projectName, path, projectPackages } =
6363
upsertProjectInput;

backend/src/schema.gql

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ input LoginUserInput {
4242
}
4343

4444
type Menu {
45-
created_at: Date!
45+
createdAt: Date!
4646
id: ID!
47-
is_active: Boolean!
48-
is_deleted: Boolean!
49-
last_updated: Date!
47+
isActive: Boolean!
48+
isDeleted: Boolean!
5049
name: String!
5150
path: String!
5251
permission: String!
52+
updatedAt: Date!
5353
}
5454

5555
type Mutation {
@@ -58,35 +58,35 @@ type Mutation {
5858
registerUser(input: RegisterUserInput!): User!
5959
removePackageFromProject(packageId: String!, projectId: String!): Boolean!
6060
updateProjectPath(newPath: String!, projectId: String!): Boolean!
61-
upsertProject(upsertProjectInput: UpsertProjectInput!): Projects!
61+
upsertProject(upsertProjectInput: UpsertProjectInput!): Project!
6262
}
6363

64-
type ProjectPackages {
65-
content: String!
66-
created_at: Date!
64+
type Project {
65+
createdAt: Date!
6766
id: ID!
68-
is_active: Boolean!
69-
is_deleted: Boolean!
70-
last_updated: Date!
71-
project_id: ID!
67+
isActive: Boolean!
68+
isDeleted: Boolean!
69+
path: String
70+
projectName: String!
71+
projectPackages: [ProjectPackages!]
72+
updatedAt: Date!
73+
userId: ID!
7274
}
7375

74-
type Projects {
75-
created_at: Date!
76+
type ProjectPackages {
77+
content: String!
78+
createdAt: Date!
7679
id: ID!
77-
is_active: Boolean!
78-
is_deleted: Boolean!
79-
last_updated: Date!
80-
path: String!
81-
projectPackages: [ProjectPackages!]
82-
project_name: String!
83-
user_id: ID!
80+
isActive: Boolean!
81+
isDeleted: Boolean!
82+
project_id: ID!
83+
updatedAt: Date!
8484
}
8585

8686
type Query {
8787
checkToken(input: CheckTokenInput!): Boolean!
88-
getProjectDetails(projectId: String!): Projects!
89-
getUserProjects: [Projects!]!
88+
getProjectDetails(projectId: String!): Project!
89+
getUserProjects: [Project!]!
9090
logout: Boolean!
9191
}
9292

@@ -101,17 +101,17 @@ type Subscription {
101101
}
102102

103103
input UpsertProjectInput {
104-
project_id: ID
105-
project_name: String!
106-
project_packages: [String!]
104+
projectId: ID
105+
projectName: String!
106+
projectPackages: [String!]
107107
}
108108

109109
type User {
110-
created_at: Date!
110+
createdAt: Date!
111111
email: String!
112112
id: ID!
113-
is_active: Boolean!
114-
is_deleted: Boolean!
115-
last_updated: Date!
113+
isActive: Boolean!
114+
isDeleted: Boolean!
115+
updatedAt: Date!
116116
username: String!
117117
}

frontend/jest.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
};

frontend/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"format": "prettier --write \"src/**/*.ts\""
1212
},
1313
"dependencies": {
14+
"@apollo/client": "^3.11.8",
1415
"@emoji-mart/data": "^1.2.1",
1516
"@emoji-mart/react": "^1.1.1",
1617
"@hookform/resolvers": "^3.9.0",
@@ -32,6 +33,7 @@
3233
"clsx": "^2.1.1",
3334
"emoji-mart": "^5.6.0",
3435
"framer-motion": "^11.5.6",
36+
"graphql": "^16.9.0",
3537
"langchain": "^0.3.2",
3638
"lucide-react": "^0.445.0",
3739
"next": "^14.2.13",
@@ -55,15 +57,18 @@
5557
"zustand": "^5.0.0-rc.2"
5658
},
5759
"devDependencies": {
60+
"@types/jest": "^29.5.14",
5861
"@types/node": "^22.5.5",
5962
"@types/react": "^18.3.8",
6063
"@types/react-dom": "^18.3.0",
6164
"@types/uuid": "^10.0.0",
6265
"autoprefixer": "^10.4.20",
6366
"eslint": "8.57.1",
6467
"eslint-config-next": "14.2.13",
68+
"jest": "^29.7.0",
6569
"postcss": "^8.4.47",
6670
"tailwindcss": "^3.4.12",
71+
"ts-jest": "^29.2.5",
6772
"typescript": "^5.6.2"
6873
}
6974
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { gql } from '@apollo/client';
2+
import client from '../../../utils/apolloClient';
3+
4+
// Define the queries and mutations
5+
6+
// Fetch user projects
7+
export const GET_USER_PROJECTS = gql`
8+
query GetUserProjects {
9+
getUserProjects {
10+
id
11+
projectName
12+
path
13+
projectPackages {
14+
id
15+
content
16+
}
17+
}
18+
}
19+
`;
20+
21+
export const getUserProjects = async (): Promise<any> => {
22+
try {
23+
const response = await client.query({
24+
query: GET_USER_PROJECTS,
25+
});
26+
return response.data.getUserProjects;
27+
} catch (error) {
28+
console.error('Error fetching user projects:', error);
29+
throw error;
30+
}
31+
};
32+
33+
// Fetch project details
34+
export const GET_PROJECT_DETAILS = gql`
35+
query GetProjectDetails($projectId: String!) {
36+
getProjectDetails(projectId: $projectId) {
37+
id
38+
projectName
39+
path
40+
projectPackages {
41+
id
42+
content
43+
}
44+
}
45+
}
46+
`;
47+
48+
export const getProjectDetails = async (projectId: string): Promise<any> => {
49+
try {
50+
const response = await client.query({
51+
query: GET_PROJECT_DETAILS,
52+
variables: { projectId },
53+
});
54+
return response.data.getProjectDetails;
55+
} catch (error) {
56+
console.error('Error fetching project details:', error);
57+
throw error;
58+
}
59+
};
60+
61+
// Upsert project (Create or Update)
62+
export const UPSERT_PROJECT = gql`
63+
mutation UpsertProject($upsertProjectInput: UpsertProjectInput!) {
64+
upsertProject(upsertProjectInput: $upsertProjectInput) {
65+
id
66+
projectName
67+
path
68+
projectPackages {
69+
id
70+
content
71+
}
72+
}
73+
}
74+
`;
75+
76+
export const upsertProject = async (upsertProjectInput: any): Promise<any> => {
77+
try {
78+
const response = await client.mutate({
79+
mutation: UPSERT_PROJECT,
80+
variables: {
81+
upsertProjectInput,
82+
},
83+
});
84+
return response.data.upsertProject;
85+
} catch (error) {
86+
console.error('Error creating/updating project:', error);
87+
throw error;
88+
}
89+
};
90+
91+
// Delete project
92+
export const DELETE_PROJECT = gql`
93+
mutation DeleteProject($projectId: String!) {
94+
deleteProject(projectId: $projectId)
95+
}
96+
`;
97+
98+
export const deleteProject = async (projectId: string): Promise<boolean> => {
99+
try {
100+
const response = await client.mutate({
101+
mutation: DELETE_PROJECT,
102+
variables: { projectId },
103+
});
104+
return response.data.deleteProject;
105+
} catch (error) {
106+
console.error('Error deleting project:', error);
107+
throw error;
108+
}
109+
};
110+
111+
// Remove package from project
112+
export const REMOVE_PACKAGE_FROM_PROJECT = gql`
113+
mutation RemovePackageFromProject($projectId: String!, $packageId: String!) {
114+
removePackageFromProject(projectId: $projectId, packageId: $packageId)
115+
}
116+
`;
117+
118+
export const removePackageFromProject = async (
119+
projectId: string,
120+
packageId: string
121+
): Promise<boolean> => {
122+
try {
123+
const response = await client.mutate({
124+
mutation: REMOVE_PACKAGE_FROM_PROJECT,
125+
variables: { projectId, packageId },
126+
});
127+
return response.data.removePackageFromProject;
128+
} catch (error) {
129+
console.error('Error removing package from project:', error);
130+
throw error;
131+
}
132+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
getUserProjects,
3+
getProjectDetails,
4+
deleteProject,
5+
upsertProject,
6+
removePackageFromProject,
7+
} from '../app/api/project/route';
8+
9+
describe('Project API', () => {
10+
let projectId: string;
11+
let packageId: string;
12+
13+
it('should upsert a project', async () => {
14+
const upsertProjectInput = {
15+
projectName: 'Test Project',
16+
projectPackages: ['Package 1', 'Package 2'],
17+
};
18+
const project = await upsertProject(upsertProjectInput);
19+
expect(project).toHaveProperty('id');
20+
projectId = project.id;
21+
console.log('Project id is: ' + projectId);
22+
packageId = project.projectPackages[0].id;
23+
});
24+
25+
it('should get user projects', async () => {
26+
const projects = await getUserProjects();
27+
expect(Array.isArray(projects)).toBe(true);
28+
expect(projects.length).toBeGreaterThan(0);
29+
});
30+
31+
it('should get project details', async () => {
32+
const projectDetails = await getProjectDetails(projectId);
33+
expect(projectDetails).toHaveProperty('id', projectId);
34+
});
35+
36+
it('should remove a package from project', async () => {
37+
const removed = await removePackageFromProject(projectId, packageId);
38+
expect(removed).toBe(true);
39+
});
40+
41+
it('should delete a project', async () => {
42+
const deleted = await deleteProject(projectId);
43+
expect(deleted).toBe(true);
44+
});
45+
});

frontend/src/utils/apolloClient.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
ApolloClient,
3+
InMemoryCache,
4+
HttpLink,
5+
ApolloLink,
6+
concat,
7+
} from '@apollo/client';
8+
9+
const httpLink = new HttpLink({
10+
// uri: process.env.NEXT_PUBLIC_API_BASE_URL,
11+
uri: 'http://localhost:8080/graphql',
12+
});
13+
14+
const authMiddleware = new ApolloLink((operation, forward) => {
15+
// Get the authentication token from local storage if it exists
16+
const token = localStorage.getItem('token');
17+
// Use the setContext method to set the HTTP headers.
18+
if (token) {
19+
operation.setContext({
20+
headers: {
21+
Authorization: `Bearer ${token}`,
22+
},
23+
});
24+
}
25+
return forward(operation);
26+
});
27+
28+
const client = new ApolloClient({
29+
link: concat(authMiddleware, httpLink),
30+
cache: new InMemoryCache(),
31+
});
32+
33+
export default client;

0 commit comments

Comments
 (0)