Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/aammya8/new account approval #104

Merged
merged 18 commits into from
Jun 15, 2024
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: 2 additions & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
echo SERVICE_ACCOUNT_KEY=${{ secrets.SERVICE_ACCOUNT_KEY }} >> .env
echo APP_PORT=${{ secrets.APP_PORT }} >> .env
echo APP_FIREBASE_CONFIG=${{ secrets.APP_FIREBASE_CONFIG }} >> .env
echo EMAIL_ADDRESS_1=${{ secrets.EMAIL_ADDRESS_1 }} >> .env
echo PASS_1=${{ secrets.PASS_1 }} >> .env
- name: Build Frontend
run: cd frontend && npm ci && npm run build
- name: Build Backend
Expand Down
19 changes: 19 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
"firebase-functions": "^4.7.0",
"mongodb": "^6.3.0",
"mongoose": "^8.3.1",
"nodemailer": "^6.9.13",
"tsc-alias": "^1.8.8"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/nodemailer": "^6.4.14",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"eslint": "^8.56.0",
Expand Down
94 changes: 91 additions & 3 deletions backend/src/controllers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import { ValidationError } from "../errors/validation";
import { Image } from "../models/image";
import UserModel from "../models/user";
import { sendApprovalEmail, sendDenialEmail } from "../util/email";
import { firebaseAdminAuth } from "../util/firebase";
import { handleImageParsing } from "../util/image";
import { deleteUserFromFirebase, deleteUserFromMongoDB } from "../util/user";
import validationErrorParser from "../util/validationErrorParser";

import { UserIdRequestBody } from "./types/types";
Expand Down Expand Up @@ -49,9 +51,6 @@
name,
accountType,
email,
// profilePicture default "default" in User constructor
// lastChangedPassword default Date.now() in User constructor
// approvalStatus default false in User constructor
});

res.status(201).json(newUser);
Expand All @@ -63,6 +62,95 @@
return;
};

export const deleteUser = async (req: Request, res: Response, nxt: NextFunction) => {
try {
const { email } = req.params;

// Find the user by email
const user = await UserModel.findOne({ email });
if (!user) {
throw new Error("User not found");
}

const userId = user._id; // _id is the uid in schema

// delete user from Firebase and MongoDB
await deleteUserFromFirebase(userId);
await deleteUserFromMongoDB(userId);

res.status(200).send("User deleted successfully");
} catch (error) {
console.error("Error deleting user:", error);
nxt(error);
}
};

export const getNotApprovedUsers = async (req: Request, res: Response, next: NextFunction) => {
try {
// const notApprovedUsers: User[] = await UserModel.find({ approvalStatus: false }).exec();
const notApprovedUsers = await UserModel.find({ approvalStatus: false }).exec();

res.status(200).json(notApprovedUsers);
} catch (error) {
console.error("Error fetching not-approved users:", error);
next(error);
}
};

export const approveUser = async (req: Request, res: Response, nxt: NextFunction) => {
try {
const { email } = req.body;

Check warning on line 102 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

const user = await UserModel.findOne({ email });

Check warning on line 104 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value
if (!user) {
return res.status(404).send("User not found");
}

const userId = user._id;

await UserModel.findByIdAndUpdate(userId, { approvalStatus: true });

// await sendApprovalEmail(email);
await sendApprovalEmail(email as string);

res.status(200).send("User approved successfully");
} catch (error) {
console.error(error);
nxt(error);
}
};

export const denyUser = async (req: Request, res: Response, nxt: NextFunction) => {
console.log("Inside denyUser controller");

try {
const { email } = req.body;

Check warning on line 127 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

console.log("Email from request body:", email);

// const user = await UserModel.findOne({ email });
const user = await UserModel.findOne({ email });

Check warning on line 132 in backend/src/controllers/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Unsafe assignment of an `any` value

if (!user) {
return res.status(404).send("User not found");
}

console.log("User object:", user);

const userId = user._id;

await UserModel.findByIdAndUpdate(userId, { approvalStatus: false });

console.log(email as string);
await sendDenialEmail(email as string);

res.status(200).send("User denied successfully");
} catch (error) {
console.error(error);
nxt(error);
}
};

export const loginUser = async (
req: Request<Record<string, never>, Record<string, never>, LoginUserRequestBody>,
res: Response,
Expand Down
9 changes: 9 additions & 0 deletions backend/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@

router.use(express.json());

router.post("/create", UserValidator.createUser, UserController.createUser);

Check warning on line 11 in backend/src/routes/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise returned in function argument where a void return was expected

router.post("/approve", [verifyAuthToken], UserController.approveUser);

Check warning on line 13 in backend/src/routes/user.ts

View workflow job for this annotation

GitHub Actions / Backend lint and style check

Promise returned in function argument where a void return was expected

router.post("/deny", [verifyAuthToken], UserController.denyUser);

router.delete("/delete/:email", [verifyAuthToken], UserController.deleteUser);

router.get("/not-approved", [verifyAuthToken], UserController.getNotApprovedUsers);

router.get("/", [verifyAuthToken], UserController.loginUser);
router.post("/editPhoto", [verifyAuthToken], UserValidator.editPhoto, UserController.editPhoto);
router.get("/getPhoto/:id", [verifyAuthToken], UserController.getPhoto);
Expand Down
59 changes: 59 additions & 0 deletions backend/src/util/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import dotenv from "dotenv";
dotenv.config();

import nodemailer from "nodemailer";

// Create a transporter object using SMTP transport
const transporter = nodemailer.createTransport({
service: "Gmail",
auth: {
user: process.env.EMAIL_ADDRESS_1,
pass: process.env.PASS_1,
},
});

export const sendApprovalEmail = async (email: string) => {
try {
await transporter.sendMail({
from: process.env.EMAIL_ADDRESS_1,
to: email,
subject: "Welcome to PIA! Your Account Has Been Approved",
// text: `Hello,
// Thank you for your interest in Plant It Again.
// We are emailing to let you know that your account
// creation request has been approved.`
html: `<p>Hello,</p>
<p>Thank you for your interest in Plant It Again.</p>
<p>We are emailing to let you know that your account creation request
has been <strong>approved</strong>.</p>`,
});
console.log("Approval email sent successfully");
} catch (error) {
console.error("Error sending approval email:", error);
}
};

export const sendDenialEmail = async (email: string) => {
console.log("Sending Denial Email");
try {
await transporter.sendMail({
from: process.env.EMAIL_ADDRESS_1,
to: email,
subject: "An Update on Your PIA Account Approval Status",
// text: `Hello,
// Thank you for your interest in Plant It Again.
// We are emailing to let you know that your account
// creation request has been denied.
// If you believe this a mistake,
// please contact us through our website`
html: `<p>Hello,</p>
<p>Thank you for your interest in Plant It Again.</p>
<p>We are emailing to let you know that your account creation request
has been <strong>denied</strong>.</p>
<p>If you believe this is a mistake, please contact us through our website.</p>`,
});
console.log("Denial email sent successfully");
} catch (error) {
console.error("Error sending denial email:", error);
}
};
13 changes: 13 additions & 0 deletions backend/src/util/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import UserModel from "../models/user";

import { firebaseAdminAuth } from "./firebase";

// delete user from Firebase
export const deleteUserFromFirebase = async (userId: string): Promise<void> => {
await firebaseAdminAuth.deleteUser(userId);
};

// delete user from MongoDB
export const deleteUserFromMongoDB = async (userId: string): Promise<void> => {
await UserModel.findByIdAndDelete(userId);
};
70 changes: 69 additions & 1 deletion frontend/src/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { APIResult, GET, PATCH, handleAPIError } from "@/api/requests";
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { APIResult, DELETE, GET, PATCH, POST, handleAPIError } from "@/api/requests";

export type User = {
uid: string;
Expand All @@ -24,6 +26,72 @@
}
};

export async function getNotApprovedUsers(firebaseToken: string): Promise<APIResult<User[]>> {
try {
const headers = createAuthHeader(firebaseToken);
console.log(headers);
const response = await GET("/user/not-approved", headers);
const json = (await response.json()) as User[];
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function approveUser(email: string, firebaseToken: string): Promise<APIResult<void>> {
try {
const headers = createAuthHeader(firebaseToken);
const response = await POST(`/user/approve`, { email }, headers);
if (response.ok) {
// return { success: true };
return { success: true, data: undefined }; // return APIResult<void> with empty data
} else {
const error = await response.json();

Check warning on line 49 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to approve user");
}
} catch (error) {
return { success: false, error: "Failed to approve user" };
}
}

export async function denyUser(email: string, firebaseToken: string): Promise<APIResult<void>> {
console.log("In frontend/src/api/user.ts denyUser()");

try {
const headers = createAuthHeader(firebaseToken);
const response = await POST(`/user/deny`, { email }, headers);
if (response.ok) {
// return { success: true };
return { success: true, data: undefined }; // return APIResult<void> with empty data
} else {
const error = await response.json();

Check warning on line 67 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to deny user");
}
} catch (error) {
return { success: false, error: "Error denying user" };
}
}

// delete user by email
export async function deleteUserByEmail(
email: string,
firebaseToken: string,
): Promise<APIResult<void>> {
try {
const headers = createAuthHeader(firebaseToken);
const response = await DELETE(`/user/delete/${encodeURIComponent(email)}`, undefined, headers);
if (response.ok) {
// return { success: true };
return { success: true, data: undefined };
} else {
const error = await response.json();

Check warning on line 87 in frontend/src/api/user.ts

View workflow job for this annotation

GitHub Actions / Frontend lint and style check

Unsafe assignment of an `any` value
throw new Error(error.message || "Failed to delete user");
}
} catch (error) {
return handleAPIError(error);
}
}

type ObjectId = string; // This is a placeholder for the actual ObjectId type
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "/api";
export async function editPhoto(
Expand Down
Loading
Loading