Skip to content

Commit 150b5b4

Browse files
committed
Merge branch 'main' of github.com:CS3219-AY2425S1/cs3219-ay2425s1-project-g16 into PEER-214
2 parents fcbf652 + 734a0f4 commit 150b5b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+606
-182
lines changed

.eslintrc.json

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
"ecmaVersion": "latest",
1212
"sourceType": "module"
1313
},
14-
"plugins": ["import", "unused-imports", "react", "@typescript-eslint", "tailwindcss"],
14+
"plugins": ["import", "unused-imports", "react", "@typescript-eslint", "tailwindcss", "simple-import-sort"],
1515
"rules": {
16-
// "@next/next/no-img-element": "off",
1716
"@typescript-eslint/no-explicit-any": "warn",
1817
"@typescript-eslint/explicit-module-boundary-types": "off",
1918
"@typescript-eslint/explicit-function-return-type": 0,
@@ -29,11 +28,21 @@
2928
"@typescript-eslint/no-empty-interface": "off",
3029
"@typescript-eslint/no-unused-vars": 0,
3130
"@typescript-eslint/no-use-before-define": 0,
31+
3232
// Basic
3333
"array-callback-return": "warn",
3434
"no-console": "warn",
35+
"no-multiple-empty-lines": ["error", { "max":1 }],
3536
"no-prototype-builtins": 0,
36-
// "no-expected-multiline": "warn", // can't find rule definition
37+
// "no-expected-multiline": "warn", // can"t find rule definition
38+
39+
"padding-line-between-statements": [
40+
"warn",
41+
{ "blankLine": "always", "prev": "*", "next": "block" },
42+
{ "blankLine": "always", "prev": "block", "next": "*" },
43+
{ "blankLine": "always", "prev": "*", "next": "block-like" },
44+
{ "blankLine": "always", "prev": "block-like", "next": "*" }
45+
],
3746

3847
// React
3948
"react/display-name": 0,
@@ -47,6 +56,8 @@
4756
}
4857
],
4958
"react-hooks/rules-of-hooks": "off",
59+
"simple-import-sort/imports": "error",
60+
"simple-import-sort/exports": "error",
5061
"tailwindcss/enforces-negative-arbitrary-values": "off",
5162
"unused-imports/no-unused-imports": "error",
5263
"unused-imports/no-unused-vars": [
@@ -66,6 +77,22 @@
6677
"rules": {
6778
"react/prop-types": "off"
6879
}
80+
},
81+
{
82+
"files": ["*.js", "*.ts", "*.tsx"],
83+
"rules": {
84+
"simple-import-sort/imports": [
85+
"error",
86+
{
87+
"groups": [
88+
["^(?:os|path|http|fs|crypto|util|events|stream|url|zlib|querystring|tls|dgram|net|dns|child_process|cluster|readline|vm|assert|buffer|process|timers)(\/.*)?$"],
89+
["^(?!(@\/|\\.\\.\/|\\.\/))"],
90+
["^@\/"],
91+
["^(?:\\.\/|\\.\\.\/|\\.)"]
92+
]
93+
}
94+
]
95+
}
6996
}
7097
]
7198
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
name: Build & publish PeerPrep images
2+
on:
3+
pull_request:
4+
types: [review_requested, ready_for_review]
5+
push:
6+
branches:
7+
- main
8+
9+
env:
10+
DOCKER_REGISTRY_USN: ay2425s1cs3219g16
11+
USER_EXPRESS_PORT: 9001
12+
QUESTION_EXPRESS_PORT: 9002
13+
COLLAB_EXPRESS_PORT: 9003
14+
MATCH_EXPRESS_PORT: 9004
15+
FRONTEND_PORT: 3000
16+
17+
jobs:
18+
changes:
19+
runs-on: ubuntu-latest
20+
# Required permissions
21+
permissions:
22+
pull-requests: read
23+
# Set job outputs to values from filter step
24+
outputs:
25+
matrix: ${{ steps.set-matrix.outputs.matrix }} # Output the matrix as a JSON string
26+
steps:
27+
- uses: actions/checkout@v4
28+
if: contains(github.ref, 'main')
29+
# For pull requests it's not necessary to checkout the code
30+
- uses: dorny/paths-filter@v3
31+
id: filter
32+
with:
33+
filters: |
34+
user:
35+
- 'backend/user/**'
36+
question:
37+
- 'backend/question/**'
38+
collaboration:
39+
- 'backend/collaboration/**'
40+
matching:
41+
- 'backend/matching/**'
42+
frontend:
43+
- 'frontend/**'
44+
- name: output-job-matrix
45+
id: set-matrix
46+
run: |
47+
is_main=${{ contains(github.ref, 'main') }}
48+
matrix=()
49+
if [[ "${{ steps.filter.outputs.user }}" == "true" || "$is_main" == "true" ]]; then
50+
config=$(jq -n \
51+
--arg pkg "user" \
52+
--arg img "$DOCKER_REGISTRY_USN/user-express" \
53+
--arg ctx "./backend/user" \
54+
--arg dkr "./backend/user/express.Dockerfile" \
55+
--arg bag "port=$USER_EXPRESS_PORT" \
56+
'{package: $pkg, image: $img, context: $ctx, dockerfile: $dkr, "build-args": $bag}')
57+
matrix+=("$config")
58+
fi
59+
if [[ "${{ steps.filter.outputs.question }}" == "true" || "$is_main" == "true" ]]; then
60+
config=$(jq -n \
61+
--arg pkg "question" \
62+
--arg img "$DOCKER_REGISTRY_USN/question-express" \
63+
--arg ctx "./backend/question" \
64+
--arg dkr "./backend/question/express.Dockerfile" \
65+
--arg bag "port=$QUESTION_EXPRESS_PORT" \
66+
'{package: $pkg, image: $img, context: $ctx, dockerfile: $dkr, "build-args": $bag}')
67+
matrix+=("$config")
68+
fi
69+
if [[ "${{ steps.filter.outputs.collaboration }}" == "true" || "$is_main" == "true" ]]; then
70+
config=$(jq -n \
71+
--arg pkg "collaboration" \
72+
--arg img "$DOCKER_REGISTRY_USN/collab-express" \
73+
--arg ctx "./backend/collaboration" \
74+
--arg dkr "./backend/collaboration/express.Dockerfile" \
75+
--arg bag "port=$COLLAB_EXPRESS_PORT" \
76+
'{package: $pkg, image: $img, context: $ctx, dockerfile: $dkr, "build-args": $bag}')
77+
matrix+=("$config")
78+
fi
79+
if [[ "${{ steps.filter.outputs.matching }}" == "true" || "$is_main" == "true" ]]; then
80+
config=$(jq -n \
81+
--arg pkg "matching" \
82+
--arg img "$DOCKER_REGISTRY_USN/match-express" \
83+
--arg ctx "./backend/matching" \
84+
--arg dkr "./backend/matching/express.Dockerfile" \
85+
--arg bag "port=$MATCH_EXPRESS_PORT" \
86+
'{package: $pkg, image: $img, context: $ctx, dockerfile: $dkr, "build-args": $bag}')
87+
matrix+=("$config")
88+
fi
89+
if [[ "${{ steps.filter.outputs.frontend }}" == "true" || "$is_main" == "true" ]]; then
90+
config=$(jq -n \
91+
--arg pkg "frontend" \
92+
--arg img "$DOCKER_REGISTRY_USN/frontend" \
93+
--arg ctx "./frontend" \
94+
--arg dkr "./frontend/frontend.Dockerfile" \
95+
--arg bag "port=$FRONTEND_PORT" \
96+
'{package: $pkg, image: $img, context: $ctx, dockerfile: $dkr, "build-args": $bag}')
97+
matrix+=("$config")
98+
fi
99+
formatted_matrix=$(echo "${matrix[@]}" | jq -cs .)
100+
echo "Outputs Generated: $formatted_matrix"
101+
echo "matrix=$formatted_matrix" >> $GITHUB_OUTPUT
102+
103+
build-and-push-image:
104+
needs: changes
105+
if: ${{ fromJson(needs.changes.outputs.matrix)[0] != null }}
106+
runs-on: ubuntu-latest
107+
strategy:
108+
fail-fast: false
109+
matrix:
110+
include: ${{ fromJson(needs.changes.outputs.matrix) }} # Use the matrix from the first job
111+
# - package: user
112+
# image: ay2425s1cs3219g16/user-express
113+
# context: ./backend/user
114+
# dockerfile: ./backend/user/express.Dockerfile
115+
# build-args: |
116+
# port=9001
117+
# - package: question
118+
# image: ay2425s1cs3219g16/question-express
119+
# context: ./backend/question
120+
# dockerfile: ./backend/question/express.Dockerfile
121+
# build-args: |
122+
# port=9002
123+
# - package: collaboration
124+
# image: ay2425s1cs3219g16/collab-express
125+
# context: ./backend/collaboration
126+
# dockerfile: ./backend/collaboration/express.Dockerfile
127+
# build-args: |
128+
# port=9003
129+
# - package: matching
130+
# image: ay2425s1cs3219g16/match-express
131+
# context: ./backend/matching
132+
# dockerfile: ./backend/matching/express.Dockerfile
133+
# build-args: |
134+
# port=9004
135+
# - package: frontend
136+
# image: ay2425s1cs3219g16/frontend
137+
# context: ./frontend
138+
# dockerfile: ./frontend/frontend.Dockerfile
139+
# build-args: |
140+
# port=3000
141+
permissions:
142+
contents: read
143+
packages: write
144+
145+
steps:
146+
- name: Checkout repository
147+
uses: actions/checkout@v4
148+
149+
- name: Set up QEMU
150+
uses: docker/setup-qemu-action@v3
151+
152+
- name: Set up Docker Buildx
153+
uses: docker/setup-buildx-action@v3
154+
155+
- name: Log in to the Container registry
156+
uses: docker/login-action@v3
157+
with:
158+
username: ${{ secrets.DOCKER_USERNAME }}
159+
password: ${{ secrets.DOCKER_PASSWORD }}
160+
161+
- name: Extract metadata (tags, labels) for Docker
162+
id: meta
163+
uses: docker/metadata-action@v5
164+
with:
165+
images: ${{ matrix.image }}
166+
167+
- name: Build and push Docker images for PeerPrep Services
168+
uses: docker/build-push-action@v6
169+
with:
170+
platforms: linux/amd64,linux/arm64
171+
context: ${{ matrix.context }}
172+
file: ${{ matrix.dockerfile }}
173+
build-args: ${{ matrix.build-args }}
174+
push: true
175+
tags: ${{ steps.meta.outputs.tags }}
176+
labels: ${{ steps.meta.outputs.labels }}
177+
cache-from: type=gha
178+
cache-to: type=gha,mode=max

.github/workflows/clear-cache.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Cleanup caches after merge
2+
on:
3+
pull_request:
4+
types:
5+
- closed
6+
workflow_dispatch:
7+
8+
jobs:
9+
cleanup:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
# `actions:write` permission is required to delete caches
13+
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
14+
actions: write
15+
contents: read
16+
17+
steps:
18+
- name: Check out code
19+
uses: actions/checkout@v4
20+
21+
- name: Cleanup cache
22+
run: |
23+
gh extension install actions/gh-actions-cache
24+
25+
REPO=${{ github.repository }}
26+
BRANCH=refs/pull/${{ github.event.pull_request.number }}/merge
27+
28+
echo "Fetching list of cache key"
29+
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH --limit 100 --sort size | cut -f 1 )
30+
31+
## Setting this to not fail the workflow while deleting cache keys.
32+
set +e
33+
echo "Deleting caches..."
34+
for cacheKey in $cacheKeysForPR
35+
do
36+
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
37+
done
38+
echo "Done"
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

backend/matching/src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ const DB_PORT = Number.parseInt(process.env.MATCHING_DB_PORT ?? '6379');
1010
export const DB_URL = `redis://${DB_HOSTNAME}:${DB_PORT}`;
1111

1212
export const NODE_ENV = process.env.NODE_ENV;
13+
14+
export const IS_MILESTONE_D4 = true;

backend/matching/src/controllers/match-request.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import type { Request, Response } from 'express';
22
import { StatusCodes } from 'http-status-codes';
33

4-
import { client } from '@/lib/db';
5-
import type { IRedisClient, IRequestMatchPayload } from '@/types';
4+
import { client, logQueueStatus } from '@/lib/db';
5+
import { logger } from '@/lib/utils';
66
import { createNotifSocket, queueingService } from '@/services';
7+
import type { IRedisClient, IRequestMatchPayload } from '@/types';
78

89
let redisClient: IRedisClient;
10+
911
export const matchRequestController = async (req: Request, res: Response) => {
1012
const payload: Partial<IRequestMatchPayload> = req.body;
1113
const { userId, difficulty, topic } = payload;
14+
1215
if (!userId || (!difficulty && !topic)) {
1316
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed Request');
1417
}
@@ -39,4 +42,6 @@ export const matchRequestController = async (req: Request, res: Response) => {
3942
socketPort: socketRoom,
4043
timestamp,
4144
});
45+
46+
logQueueStatus(logger, redisClient, `Queue Status Before Matching: <PLACEHOLDER>`);
4247
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './client';
2+
export * from './log-queue-status';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { IS_MILESTONE_D4 } from '@/config';
2+
3+
import { client } from './client';
4+
import { STREAM_NAME } from './constants';
5+
6+
export const getQueueStatusLog = async (redisClient: typeof client) => {
7+
const queueStatus = await redisClient.xRange(STREAM_NAME, '-', '+');
8+
const messages = queueStatus.map((v) => v.message);
9+
return JSON.stringify(messages);
10+
};
11+
12+
export const logQueueStatus = async (
13+
// eslint-disable-next-line
14+
logger: { info: (...m: any[]) => void },
15+
redisClient: typeof client,
16+
message: string
17+
) => {
18+
if (!IS_MILESTONE_D4) {
19+
return;
20+
}
21+
22+
const queueStatusLog = await getQueueStatusLog(redisClient);
23+
logger.info(message.replace('<PLACEHOLDER>', queueStatusLog));
24+
};

backend/matching/src/lib/db/seed.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SchemaFieldTypes } from 'redis';
2+
23
import { client } from './client';
34
import {
45
MATCH_PREFIX,
@@ -18,18 +19,23 @@ const logger = {
1819

1920
const main = async () => {
2021
const redisClient = await client.connect();
22+
2123
if (!redisClient) {
2224
return;
2325
}
26+
2427
logger.info('Connected');
2528
const isSeeded = await redisClient.hGetAll(SEED_KEY);
29+
2630
if (Object.keys(isSeeded).length > 0) {
2731
const { timeStamp, value } = isSeeded;
32+
2833
if (value === 'true') {
2934
logger.info('Seeded at: ' + new Date(timeStamp).toLocaleString());
3035
return;
3136
}
3237
}
38+
3339
// Set Search Index
3440
await redisClient.ft.create(
3541
POOL_INDEX,

backend/matching/src/routes/match.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { matchRequestController } from '@/controllers/match-request';
21
import { Router } from 'express';
32

3+
import { matchRequestController } from '@/controllers/match-request';
4+
45
const route = Router();
56

67
route.post('/request', matchRequestController);

0 commit comments

Comments
 (0)