Skip to content

Commit 198ffd4

Browse files
authored
Merge branch 'main' into ashley/overall-cleanup
2 parents 8090b3b + 484153b commit 198ffd4

11 files changed

Lines changed: 424 additions & 76 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ package-lock.json
66
yarn.lock
77
*.DS_Store
88
*.pem
9+
dumps/
910
firebase-admin-service-account.json

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
},
2626
"homepage": "https://github.com/cuappdev/resell-backend#readme",
2727
"devDependencies": {
28-
"@types/express": "^4.17.13",
28+
"@types/body-parser": "^1.19.6",
29+
"@types/express": "^4.17.25",
2930
"@types/faker": "^5.5.5",
3031
"@types/jest": "^27.0.2",
32+
"@types/multer": "^2.0.0",
3133
"@types/node": "^16.9.0",
3234
"@types/node-fetch": "^2.6.1",
3335
"@types/swagger-ui-express": "^4.1.7",
@@ -46,12 +48,15 @@
4648
"@tensorflow-models/universal-sentence-encoder": "^1.3.3",
4749
"@tensorflow/tfjs": "^4.2.0",
4850
"@tensorflow/tfjs-node": "^4.22.0",
49-
"class-validator": "^0.14.0",
51+
"body-parser": "^2.2.2",
52+
"class-transformer": "^0.5.1",
53+
"class-validator": "^0.14.3",
5054
"cors": "^2.8.6",
5155
"dotenv": "^10.0.0",
52-
"express": "^4.17.2",
56+
"express": "^4.22.1",
5357
"faker": "^5.5.3",
5458
"firebase-admin": "^13.1.0",
59+
"multer": "^2.0.2",
5560
"node-fetch": "^2.6.1",
5661
"pg": "^8.7.1",
5762
"pgvector": "^0.2.0",

src/api/controllers/NotifController.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ export class NotifController {
6262
return this.notifService.sendRequestMatchNotification(matchRequest);
6363
}
6464

65+
@Post("markAsRead/id/:id")
66+
async markAsRead(
67+
@CurrentUser() user: UserModel,
68+
@Params() params: { id: string },
69+
) {
70+
return this.notifService.markAsRead(user.firebaseUid, params.id);
71+
}
72+
6573
@Delete("id/:id")
6674
async deleteNotification(
6775
@CurrentUser() user: UserModel,

src/api/controllers/ReportController.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
GetReportsResponse,
2020
} from "../../types/ApiResponses";
2121
import { ReportModel } from "../../models/ReportModel";
22-
import { report } from "process";
2322
import { UuidParam } from "../validators/GenericRequests";
2423

2524
@JsonController("report/")

src/api/middlewares/ErrorHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ export class ErrorHandler implements ExpressErrorMiddlewareInterface {
1313
error: Error,
1414
request: express.Request,
1515
response: express.Response,
16-
next: (err?: any) => any,
16+
next: (err?: unknown) => unknown,
1717
): void {
1818
handleError(error, request, response, next);
1919
}
2020
}
2121

2222
function handleError(
2323
error: Error,
24-
request: express.Request,
24+
_request: express.Request,
2525
response: express.Response,
2626
next: express.NextFunction,
2727
) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
Action,
3+
ForbiddenError,
4+
NotFoundError,
5+
UnauthorizedError,
6+
} from "routing-controllers";
7+
import { firebaseAdmin } from "../../firebase";
8+
import { getManager } from "typeorm";
9+
import { UserModel } from "../../models/UserModel";
10+
11+
export const FirebaseCurrentUserChecker = async (
12+
action: Action,
13+
): Promise<UserModel> => {
14+
const authHeader = action.request.headers["authorization"];
15+
if (!authHeader) {
16+
throw new UnauthorizedError("No authorization token provided");
17+
}
18+
const token = authHeader.split(" ")[1];
19+
if (!token) {
20+
throw new UnauthorizedError("Invalid authorization token format");
21+
}
22+
23+
try {
24+
// Verify the token using Firebase Admin SDK
25+
const decodedToken = await firebaseAdmin.auth().verifyIdToken(token);
26+
const userId = decodedToken.uid;
27+
const email = decodedToken.email;
28+
// Enforce Cornell email domain restriction
29+
if (email && !email.endsWith("@cornell.edu")) {
30+
throw new ForbiddenError("Only Cornell email addresses are allowed");
31+
}
32+
// Fetch and return the user from the database
33+
const user = await getManager().findOne(UserModel, {
34+
firebaseUid: userId,
35+
});
36+
if (!user) {
37+
throw new NotFoundError("User not found");
38+
}
39+
return user;
40+
} catch (error) {
41+
throw new UnauthorizedError("Invalid or expired authorization token");
42+
}
43+
};

src/api/validators/GenericRequests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IsEmail, IsNumber, IsString, IsUUID } from "class-validator";
1+
import { IsEmail, IsString, IsUUID } from "class-validator";
22

33
import { Uuid } from "../../types";
44

src/app.ts

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,71 @@
1-
import "reflect-metadata";
1+
// Load environment and Firebase configuration first
22
import "dotenv/config";
3+
import "./firebase";
4+
import "reflect-metadata";
35

4-
import dotenv from "dotenv";
56
import {
67
createExpressServer,
7-
ForbiddenError,
8-
UnauthorizedError,
98
useContainer as routingUseContainer,
10-
HttpError,
119
} from "routing-controllers";
12-
1310
import { getManager, useContainer } from "typeorm";
1411
import { Container } from "typeorm-typedi-extensions";
15-
import { Express } from "express";
12+
import { Express, Request, Response } from "express";
1613
import * as swaggerUi from "swagger-ui-express";
17-
import * as path from "path";
18-
import * as admin from "firebase-admin";
19-
20-
dotenv.config();
21-
22-
const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH || "";
23-
const serviceAccount = require(serviceAccountPath);
24-
25-
if (!serviceAccountPath) {
26-
throw new Error(
27-
"FIREBASE_SERVICE_ACCOUNT_PATH environment variable is not set.",
28-
);
29-
}
30-
31-
if (!admin.apps.length) {
32-
admin.initializeApp({
33-
credential: admin.credential.cert(serviceAccount),
34-
});
35-
}
36-
37-
export { admin }; // Export the admin instance
14+
import swaggerDocument from "../swagger.json";
3815

3916
import { controllers } from "./api/controllers";
4017
import { middlewares } from "./api/middlewares";
41-
import { UserModel } from "./models/UserModel";
42-
import { ReportController } from "./api/controllers/ReportController";
18+
import { FirebaseCurrentUserChecker } from "./api/middlewares/FirebaseAuth";
4319
import resellConnection from "./utils/DB";
44-
import { ReportService } from "./services/ReportService";
45-
import { reportToString } from "./utils/Requests";
46-
47-
dotenv.config();
4820

21+
const port = process.env.PORT ?? 3000;
22+
const app: Express = createExpressServer({
23+
cors: true,
24+
routePrefix: "/api/",
25+
controllers: controllers,
26+
middlewares: middlewares,
27+
defaults: {
28+
paramOptions: {
29+
required: true, // Make all params required by default
30+
},
31+
},
32+
validation: true,
33+
development: process.env.NODE_ENV !== "production",
34+
defaultErrorHandler: false,
35+
currentUserChecker: FirebaseCurrentUserChecker,
36+
});
37+
38+
/**
39+
* Setup Swagger docs
40+
*/
41+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
42+
console.log(
43+
`Swagger documentation available at http://localhost:${port}/api-docs`,
44+
);
45+
46+
/**
47+
* Health check endpoint
48+
*/
49+
app.get("/health", async (_req: Request, res: Response) => {
50+
const manager = getManager();
51+
try {
52+
await manager.query("SELECT 1");
53+
res.status(200).json({ status: "healthy", database: "Connected" });
54+
} catch (error) {
55+
res
56+
.status(500)
57+
.json({ status: "Error", database: "Not connected", error: error });
58+
}
59+
});
60+
61+
// Setup dependency injection containers
62+
routingUseContainer(Container);
63+
useContainer(Container);
64+
65+
// Initialize and start application
4966
async function main() {
50-
routingUseContainer(Container);
51-
useContainer(Container);
52-
53-
await resellConnection().catch((error: any) => {
67+
// Initialize database connection
68+
await resellConnection().catch((error: unknown) => {
5469
console.log(error);
5570
throw new Error("Connection to DB failed. Check console output");
5671
});

src/firebase.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as firebaseAdmin from "firebase-admin";
2+
const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH || "";
3+
const serviceAccount = require(serviceAccountPath);
4+
5+
if (!serviceAccountPath) {
6+
throw new Error(
7+
"FIREBASE_SERVICE_ACCOUNT_PATH environment variable is not set.",
8+
);
9+
}
10+
11+
if (!firebaseAdmin.apps.length) {
12+
firebaseAdmin.initializeApp({
13+
credential: firebaseAdmin.credential.cert(serviceAccount),
14+
});
15+
}
16+
17+
export { firebaseAdmin };

src/services/NotifService.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,22 @@ export class NotifService {
237237
});
238238
}
239239

240+
async markAsRead(userId: string, notifId: string) {
241+
return this.transactions.readWrite(async (transactionalEntityManager) => {
242+
const notifRepository = Repositories.notification(
243+
transactionalEntityManager,
244+
);
245+
const notif = await notifRepository.findOne({ where: { id: notifId } });
246+
if (!notif) {
247+
throw new NotFoundError("Notification not found");
248+
}
249+
if (notif.userId !== userId) {
250+
throw new NotFoundError("Notification not found");
251+
}
252+
return await notifRepository.markAsRead(notifId);
253+
});
254+
}
255+
240256
async deleteNotification(userId: string, notifId: string) {
241257
return this.transactions.readWrite(async (transactionalEntityManager) => {
242258
const notifRepository = Repositories.notification(

0 commit comments

Comments
 (0)