Skip to content

Commit a863a63

Browse files
authored
Merge pull request #68 from CS3219-AY2324S1/62-add-functionality-for-maintainer
62 add functionality for maintainer
2 parents f39b522 + 066a5c2 commit a863a63

File tree

13 files changed

+378
-14
lines changed

13 files changed

+378
-14
lines changed

UserService/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import cors from "cors";
66
// import { firebaseConfig } from "./firebase/firebase.config";
77
// import { Socket, Server } from 'socket.io';
88
// import { Server as ServerHttp } from 'http';
9-
import { handleCreateUser, handleDeleteUser, handleGetUser, handleUpdateUser } from "./user/user.controller";
9+
10+
import { handleCreateUser, handleDeleteUser, handleGetUser, handleUpdateUser, handleCreateAdminUser, handleGetAdminUsers } from "./user/user.controller";
1011
const app = express();
1112
const port = 3004;
1213
app.use(cors());
@@ -23,6 +24,8 @@ app.get("/", (req, res) => {
2324
// const db = getFirestore();
2425

2526
app.post("/user", handleCreateUser);
27+
app.get("/adminusers", handleGetAdminUsers);
28+
app.post("/useradmin", handleCreateAdminUser);
2629
app.get("/user", handleGetUser);
2730
app.put("/user", handleUpdateUser);
2831
app.delete("/user", handleDeleteUser);

UserService/src/user/user.controller.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Request, Response } from "express";
2-
import { createUser, delUser, getUser, updateUser } from "./user.service";
2+
3+
import { createAdminUser, delUser, createUser, getAdminUsers, getUser, updateUser } from "./user.service";
34

45
export async function handleCreateUser(req: Request, res: Response) {
56
try {
@@ -13,6 +14,18 @@ export async function handleCreateUser(req: Request, res: Response) {
1314
}
1415
}
1516

17+
export async function handleCreateAdminUser(req: Request, res: Response) {
18+
try {
19+
const { email } = req.body;
20+
console.log(`creating admin user ${email}`);
21+
const user = await createAdminUser(email);
22+
res.status(200).send(user);
23+
} catch (error) {
24+
console.error(error);
25+
res.status(500).send(error);
26+
}
27+
}
28+
1629
export async function handleGetUser(req: Request, res: Response) {
1730
try {
1831
console.log(req.query.email);
@@ -31,6 +44,17 @@ export async function handleGetUser(req: Request, res: Response) {
3144
}
3245
}
3346

47+
export async function handleGetAdminUsers(req: Request, res: Response) {
48+
try {
49+
console.log(`getting admin users`);
50+
const result = await getAdminUsers();
51+
res.status(200).send(result);
52+
} catch (error) {
53+
console.error(error);
54+
res.status(500).send(error);
55+
}
56+
}
57+
3458
export async function handleUpdateUser(req: Request, res: Response) {
3559
try {
3660
const email = req.body.params.email;

UserService/src/user/user.service.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { initializeApp } from "firebase/app";
2-
import { doc, getDoc, getFirestore, setDoc, updateDoc, deleteDoc } from "firebase/firestore";
2+
import { doc, getDoc, getFirestore, setDoc, updateDoc, where, query, collection, getDocs, deleteDoc } from "firebase/firestore";
33
import { firebaseConfig } from "../firebase/firebase.config";
44
import { getAuth } from "firebase/auth";
55
const app = initializeApp(firebaseConfig);
@@ -38,6 +38,29 @@ export async function createUser(email: string): Promise<User> {
3838
}
3939
}
4040

41+
export async function createAdminUser(email: string): Promise<User> {
42+
try {
43+
await setDoc(doc(db, "users", email), {
44+
email: email,
45+
name: "-",
46+
year: "-",
47+
major: "-",
48+
role: "admin",
49+
completed: 0,
50+
});
51+
return Promise.resolve({
52+
email: email,
53+
name: "-",
54+
year: "-",
55+
major: "-",
56+
role: "admin",
57+
completed: 0,
58+
});
59+
} catch (error) {
60+
return Promise.reject(error);
61+
}
62+
}
63+
4164
export async function getUser(email: string): Promise<User> {
4265
try {
4366
const data = await getDoc(doc(db, "users", email));
@@ -58,13 +81,42 @@ export async function getUser(email: string): Promise<User> {
5881
}
5982
}
6083

84+
export async function getAdminUsers(): Promise<User[]> {
85+
try {
86+
const q = query(collection(db, "users"), where("role", "==", "admin"));
87+
const querySnapshot = await getDocs(q);
88+
let adminsArray: (User)[];
89+
adminsArray = []
90+
querySnapshot.forEach((doc) => {
91+
const user = doc.data()
92+
adminsArray = adminsArray.concat([{
93+
email: user.email,
94+
name: user.name ? user.name : undefined,
95+
year: user.year ? user.year : undefined,
96+
major: user.major ? user.major : undefined,
97+
role: user.role,
98+
completed: user.completed,
99+
}])
100+
})
101+
102+
if (querySnapshot) {
103+
return Promise.resolve(adminsArray);
104+
105+
}
106+
return Promise.reject("no such user");
107+
} catch (error) {
108+
return Promise.reject(error);
109+
}
110+
}
111+
61112
export async function updateUser(email: string, params: any): Promise<User> {
62113
try {
63114
const document = doc(db, "users", email);
64115
await updateDoc(document, {
65116
name: params.name,
66117
year: params.year,
67-
major: params.major
118+
major: params.major,
119+
role: params.role
68120
})
69121
const data = await getDoc(doc(db, "users", email));
70122
const user = data.data();

frontend/src/api/user/data.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { UserModel, Values } from "./model";
44
export const createUser = (email: string) =>
55
UserHttpClient.post<UserModel>("/user", { email: email });
66

7+
export const getAllAdminUsers = () => UserHttpClient.get<UserModel[]>("/adminusers");
8+
9+
export const createAdminUser = (email: string) =>
10+
UserHttpClient.post<UserModel>("/useradmin", { email: email });
11+
712
export const getUser = (email: string) =>
813
UserHttpClient.get<UserModel>("/user", { params: { email: email } });
914

frontend/src/api/user/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ export interface UserModel {
88
}
99

1010
export interface Values {
11-
[key: string]: string;
11+
[key: string]: string | number;
1212
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ReactNode } from "react";
2+
import { Navigate } from "react-router-dom";
3+
import { useAuth } from "./auth.context";
4+
5+
interface AuthGuardProps {
6+
children: ReactNode;
7+
}
8+
9+
export default function MaintainerGuard({ children }: AuthGuardProps) {
10+
const { user } = useAuth();
11+
if (!(user?.role == 'maintainer')) {
12+
return <Navigate to="/login" />;
13+
}
14+
return <>{children}</>;
15+
}

frontend/src/auth/auth.context.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { deleteUser, registerUser, signIn, signOut } from "../api/auth";
1111
import { useLocalStorage } from "./useLocalStorage";
1212
import { useNavigate } from "react-router-dom";
1313
import { AxiosError } from "axios";
14-
import { createUser, getUser, UserModel } from "../api/user";
14+
import { createAdminUser, createUser, getUser, UserModel } from "../api/user";
1515

1616
interface AuthContextData {
1717
user: UserModel | undefined;
1818
setUser: any | undefined,
1919
activeUser: User | undefined;
2020
error: string;
2121
signUp: (email: string, password: string) => void;
22+
signUpAdmin: (email: string, password: string) => void;
2223
login: (email: string, password: string) => void;
2324
logout: () => void;
2425
removeAccount: () => void;
@@ -34,6 +35,7 @@ const AuthContext = createContext<AuthContextData>({
3435
activeUser: undefined,
3536
error: "",
3637
signUp: (email: string, password: string) => undefined,
38+
signUpAdmin: (email: string, password: string) => undefined,
3739
login: (email: string, password: string) => undefined,
3840
logout: () => undefined,
3941
removeAccount: () => undefined
@@ -70,6 +72,33 @@ export function AuthContextProvider({ children }: AuthContextProviderProps) {
7072
[setUser]
7173
);
7274

75+
const signUpAdmin = useCallback(
76+
async (email: string, password: string) => {
77+
try {
78+
const response = await registerUser({
79+
email: email,
80+
password: password,
81+
});
82+
const u: User = response.data.user;
83+
if (!u.email) {
84+
throw new Error("user returned without email");
85+
}
86+
console.log('creating admin')
87+
const fetchedUser = await createAdminUser(u.email);
88+
console.log('admin created')
89+
// setActiveUser(u)
90+
// setUser(fetchedUser.data);
91+
} catch (e) {
92+
if (e instanceof AxiosError && e.response) {
93+
setError(e.response.data.code);
94+
} else if (e instanceof Error) {
95+
setError(e.message);
96+
}
97+
}
98+
},
99+
[setUser]
100+
);
101+
73102
const login = useCallback(
74103
async (email: string, password: string) => {
75104
try {
@@ -126,8 +155,8 @@ export function AuthContextProvider({ children }: AuthContextProviderProps) {
126155
}, [setUser, navigate]);
127156

128157
const authContextProviderValue = useMemo(
129-
() => ({ user, setUser, activeUser, error, signUp, login, logout, removeAccount }),
130-
[user, setUser, activeUser, error, signUp, login, logout, removeAccount]
158+
() => ({ user, setUser, activeUser, error, signUp, signUpAdmin, login, logout, removeAccount }),
159+
[user, setUser, activeUser, error, signUp, signUpAdmin, login, logout, removeAccount]
131160
);
132161

133162
return (
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Select, MenuItem, TablePagination, SelectChangeEvent, Grid, Typography, Button } from '@mui/material';
3+
import { useData } from '../data/data.context';
4+
import { updateUser } from '../api/user';
5+
6+
interface AdminUser {
7+
email: string;
8+
name?: string;
9+
year?: string;
10+
major?: string;
11+
role: string;
12+
completed: number;
13+
}
14+
15+
const ITEMS_PER_PAGE_OPTIONS = [5, 10]; // Number of items to display per page
16+
17+
const AdminUsersTable: React.FC = () => {
18+
const [adminUsersData, setAdminUsers] = useState<AdminUser[]>([]);
19+
const [currentPage, setCurrentPage] = useState<number>(1);
20+
const [itemsPerPage, setItemsPerPage] = useState<number>(ITEMS_PER_PAGE_OPTIONS[0]);
21+
const { adminUsers, getAdminUsers } = useData();
22+
23+
useEffect(() => {
24+
async function getAdmins() {
25+
getAdminUsers();
26+
}
27+
getAdmins();
28+
}, []);
29+
30+
useEffect(() => {
31+
setAdminUsers(adminUsers);
32+
console.log('check')
33+
}, [adminUsers]);
34+
35+
const handlePageChange = (event: unknown, newPage: number) => {
36+
setCurrentPage(newPage);
37+
};
38+
39+
const handleChangeItemsPerPage = (
40+
event: SelectChangeEvent<unknown>,
41+
) => {
42+
setItemsPerPage(event.target.value as number);
43+
setCurrentPage(1);
44+
};
45+
46+
const removeAdmin = async (admin: AdminUser, index: number) => {
47+
await updateUser({
48+
email: admin?.email ? admin?.email : "",
49+
name: admin?.name ? admin?.name : "",
50+
year: admin?.year ? admin?.year : "",
51+
major: admin?.major ? admin.major : "",
52+
role: "user",
53+
completed: admin?.completed ? admin.completed : 0,
54+
})
55+
setAdminUsers(adminUsersData.filter((e, i) => i !== index));
56+
}
57+
58+
const indexOfLastAdmin = currentPage * itemsPerPage;
59+
const indexOfFirstAdmin = indexOfLastAdmin - itemsPerPage;
60+
const currentAdmins = adminUsersData.slice(indexOfFirstAdmin, indexOfLastAdmin);
61+
62+
return (
63+
<><div style={{ maxHeight: '400px', overflowY: 'auto', width: '100%' }}>
64+
65+
<TableContainer component={Paper} style={{ margin: '10px', padding: '10px' }}>
66+
<Grid container>
67+
<Grid item xs={3}>
68+
<Typography fontWeight={600}>Administrators:</Typography>
69+
</Grid>
70+
</Grid>
71+
<Table style={{ minWidth: 650, fontSize: '14px' }}>
72+
<TableHead>
73+
<TableRow >
74+
<TableCell>Email</TableCell>
75+
<TableCell>Name</TableCell>
76+
<TableCell>Year</TableCell>
77+
<TableCell>Major</TableCell>
78+
{/* <TableCell>Role</TableCell> */}
79+
{/* <TableCell>Completed</TableCell> */}
80+
<TableCell>Remove Privileges</TableCell>
81+
</TableRow>
82+
</TableHead>
83+
<TableBody>
84+
{currentAdmins.map((admin:AdminUser, index:number) => (
85+
<TableRow key={index}>
86+
<TableCell>{admin.email}</TableCell>
87+
<TableCell>{admin.name}</TableCell>
88+
<TableCell>{admin.year}</TableCell>
89+
<TableCell>{admin.major}</TableCell>
90+
<TableCell>
91+
<Button onClick={() => removeAdmin(admin, index)}>
92+
Confirm
93+
</Button>
94+
</TableCell>
95+
96+
{/* <TableCell>{admin.role}</TableCell> */}
97+
{/* <TableCell>{admin.completed}</TableCell> */}
98+
</TableRow>
99+
))}
100+
</TableBody>
101+
</Table>
102+
</TableContainer>
103+
</div><div style={{ display: 'flex', alignItems: 'center' }}>
104+
<Select
105+
value={itemsPerPage}
106+
onChange={handleChangeItemsPerPage}
107+
style={{ marginTop: '10px' }}
108+
>
109+
{ITEMS_PER_PAGE_OPTIONS.map((option) => (
110+
<MenuItem key={option} value={option}>
111+
{`${option} per page`}
112+
</MenuItem>
113+
))}
114+
</Select>
115+
116+
<TablePagination
117+
rowsPerPageOptions={[]}
118+
component="div"
119+
count={adminUsersData.length}
120+
rowsPerPage={itemsPerPage}
121+
page={currentPage - 1}
122+
onPageChange={handlePageChange} />
123+
</div></>
124+
125+
);
126+
};
127+
128+
export default AdminUsersTable;

0 commit comments

Comments
 (0)