Skip to content

Commit 735fe93

Browse files
authored
Merge pull request #104 from CS3219-AY2425S1/feat/91-cloud-deploy
Feat/91 cloud deploy
2 parents 4767563 + e21e273 commit 735fe93

File tree

10 files changed

+183
-60
lines changed

10 files changed

+183
-60
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ jobs:
3030
- name: "Trigger Cloud Build"
3131
env: # have to use env here as cant use secrets in kubernetes for nextJS
3232
NEXT_PUBLIC_API_GATEWAY_URL: ${{ secrets.NEXT_PUBLIC_API_GATEWAY_URL }}
33+
NEXT_PUBLIC_GITHUB_CLIENT_ID: ${{ secrets.NEXT_PUBLIC_GITHUB_CLIENT_ID }}
3334
run: |
34-
gcloud builds submit --config cloudbuild.yaml --substitutions _NEXT_PUBLIC_API_GATEWAY_URL="$NEXT_PUBLIC_API_GATEWAY_URL"
35+
gcloud builds submit --config cloudbuild.yaml --substitutions _NEXT_PUBLIC_API_GATEWAY_URL="$NEXT_PUBLIC_API_GATEWAY_URL",_NEXT_PUBLIC_GITHUB_CLIENT_ID="$NEXT_PUBLIC_GITHUB_CLIENT_ID",_NEXT_PUBLIC_SOCK_SERVER_URL="$NEXT_PUBLIC_SOCK_SERVER_URL"

cloudbuild.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ steps:
1010
"asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/peerprep-fe:latest",
1111
"--build-arg",
1212
"NEXT_PUBLIC_API_GATEWAY_URL=${_NEXT_PUBLIC_API_GATEWAY_URL}",
13+
"--build-arg",
14+
"NEXT_PUBLIC_GITHUB_CLIENT_ID=${_NEXT_PUBLIC_GITHUB_CLIENT_ID}",
1315
"-f",
1416
"peerprep-fe/Dockerfile",
1517
"peerprep-fe",
@@ -63,6 +65,26 @@ steps:
6365
"asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/user-svc:latest",
6466
]
6567

68+
# Build and push api-gateway
69+
- name: "gcr.io/cloud-builders/docker"
70+
args:
71+
[
72+
"build",
73+
"-t",
74+
"asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest",
75+
"-f",
76+
"api-gateway/Dockerfile",
77+
"api-gateway",
78+
]
79+
80+
# Push the api-gateway image to Artifact Registry
81+
- name: "gcr.io/cloud-builders/docker"
82+
args:
83+
[
84+
"push",
85+
"asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest",
86+
]
87+
6688
# Step 2: Clean up and deploy to GKE
6789

6890
# Clean up before deploying peerprep-fe
@@ -104,6 +126,19 @@ steps:
104126
- --location=asia-southeast1
105127
- --cluster=cs3219-g11-peerprep-kubes
106128

129+
# Clean up before deploying api-gateway
130+
- name: "ubuntu"
131+
args: ["rm", "-rf", "output"]
132+
133+
# Deploy api-gateway to Google Kubernetes Engine (GKE)
134+
- name: "gcr.io/cloud-builders/gke-deploy"
135+
args:
136+
- run
137+
- --filename=k8s/api-gateway.yml
138+
- --image=asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest
139+
- --location=asia-southeast1
140+
- --cluster=cs3219-g11-peerprep-kubes
141+
107142
# Step 3: update the deployments with the new images
108143

109144
# Update the peerprep-fe deployment
@@ -139,8 +174,20 @@ steps:
139174
- "CLOUDSDK_COMPUTE_ZONE=asia-southeast1"
140175
- "CLOUDSDK_CONTAINER_CLUSTER=cs3219-g11-peerprep-kubes"
141176

177+
# Update the api-gateway deployment
178+
- name: "gcr.io/cloud-builders/kubectl"
179+
args:
180+
- "set"
181+
- "image"
182+
- "deployment/api-gateway"
183+
- "api-gateway=asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest"
184+
env:
185+
- "CLOUDSDK_COMPUTE_ZONE=asia-southeast1"
186+
- "CLOUDSDK_CONTAINER_CLUSTER=cs3219-g11-peerprep-kubes"
187+
142188
substitutions:
143189
_NEXT_PUBLIC_API_GATEWAY_URL: ""
190+
_NEXT_PUBLIC_GITHUB_CLIENT_ID: ""
144191

145192
options:
146193
dynamic_substitutions: true
@@ -149,3 +196,4 @@ images:
149196
- "asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/peerprep-fe:latest"
150197
- "asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/question-svc:latest"
151198
- "asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/user-svc:latest"
199+
- "asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest"

k8s/api-gateway.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: api-gateway
5+
labels:
6+
type: backend
7+
app: api-gateway
8+
spec:
9+
selector:
10+
matchLabels:
11+
type: backend
12+
app: api-gateway
13+
template:
14+
metadata:
15+
name: api-gateway
16+
labels:
17+
type: backend
18+
app: api-gateway
19+
spec:
20+
containers:
21+
- name: api-gateway
22+
image: asia-southeast1-docker.pkg.dev/cs3219-g11-peerprep/cs3219-g11-repo/api-gateway:latest
23+
imagePullPolicy: Always
24+
resources:
25+
requests:
26+
cpu: "100m"
27+
memory: "128Mi"
28+
limits:
29+
cpu: "200m"
30+
memory: "256Mi"
31+
ports:
32+
- containerPort: 8001
33+
envFrom:
34+
- secretRef:
35+
name: api-gateway-config
36+
dnsPolicy: ClusterFirst
37+
---
38+
apiVersion: v1
39+
kind: Service
40+
metadata:
41+
name: api-gateway
42+
spec:
43+
type: LoadBalancer
44+
ports:
45+
- port: 8001
46+
targetPort: 8001
47+
selector:
48+
type: backend
49+
app: api-gateway
50+
---
51+
apiVersion: autoscaling/v2
52+
kind: HorizontalPodAutoscaler
53+
metadata:
54+
name: api-gateway
55+
spec:
56+
scaleTargetRef:
57+
apiVersion: apps/v1
58+
kind: Deployment
59+
name: api-gateway
60+
minReplicas: 1
61+
maxReplicas: 5
62+
metrics:
63+
- type: Resource
64+
resource:
65+
name: cpu
66+
target:
67+
type: Utilization
68+
averageUtilization: 80

k8s/peerprep-config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ metadata:
55
type: Opaque
66
stringData:
77
NEXT_PUBLIC_API_GATEWAY_URL: "${_NEXT_PUBLIC_API_GATEWAY_URL}"
8+
NEXT_PUBLIC_GITHUB_CLIENT_ID: "${_NEXT_PUBLIC_GITHUB_CLIENT_ID}"
89
# Removed individual service URLs as they are no longer needed

matching-service/src/lib/hasher.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import crypto from 'crypto';
1+
import crypto from "crypto";
22

33
export const generateMatchId = (userId: string, matchId: string): string => {
4-
// Concatenate the IDs
5-
const combinedIds = `${userId}-${matchId}`;
4+
// Concatenate the IDs
5+
const combinedIds = `${userId}-${matchId}`;
66

7-
// Hash the concatenated string using SHA-256
8-
return crypto.createHash('sha256').update(combinedIds).digest('hex');
9-
};
7+
// Hash the concatenated string using SHA-256
8+
return crypto.createHash("sha256").update(combinedIds).digest("hex");
9+
};

matching-service/src/services/matchingService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const processOldUsers = async (): Promise<void> => {
5555
const match = await checkMatch(`difficulty:${user.difficulty}`);
5656
if (match) {
5757
// Send to user subscribed queues if there is a match
58-
const matchId = generateMatchId(match._id, user._id)
58+
const matchId = generateMatchId(match._id, user._id);
5959
await sendToQueue(match._id, {
6060
status: "matched",
6161
matchId: matchId,
@@ -102,7 +102,7 @@ export const processNewMessage = async (user: User): Promise<void> => {
102102
const match = await checkMatch(`topic:${user.topic}`);
103103
// match found
104104
if (match) {
105-
const matchId = generateMatchId(match._id, user._id)
105+
const matchId = generateMatchId(match._id, user._id);
106106
await sendToQueue(match._id, {
107107
status: "matched",
108108
matchId: matchId,

peerprep-fe/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ ENV PORT=3000
3131

3232
# Accept build argument
3333
ARG NEXT_PUBLIC_API_GATEWAY_URL
34+
ARG NEXT_PUBLIC_GITHUB_CLIENT_ID
3435

3536
# Set environment variable
3637
ENV NEXT_PUBLIC_API_GATEWAY_URL=$NEXT_PUBLIC_API_GATEWAY_URL
38+
ENV NEXT_PUBLIC_GITHUB_CLIENT_ID=$NEXT_PUBLIC_GITHUB_CLIENT_ID
3739

3840
COPY . .
3941
RUN pnpm build

peerprep-fe/src/app/collaboration/page.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,34 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
1919
import { useSearchParams } from 'next/navigation';
2020
import * as Y from 'yjs';
2121
import { WebsocketProvider } from 'y-websocket';
22-
import { MonacoBinding } from 'y-monaco'
22+
import { MonacoBinding } from 'y-monaco';
2323
import { editor as MonacoEditor } from 'monaco-editor';
2424

2525
const CollaborationPage = () => {
26-
const [selectionProblem, setSelectionProblem] = useState<Problem | null>(null);
26+
const [selectionProblem, setSelectionProblem] = useState<Problem | null>(
27+
null,
28+
);
2729
const searchParams = useSearchParams();
2830
const matchId = searchParams.get('matchId');
2931
const [language, setLanguage] = useState(SUPPORTED_PROGRAMMING_LANGUAGES[0]);
3032
const { problems, isLoading } = useFilteredProblems();
31-
const [connectedClients, setConnectedClients] = useState<Set<number>>(new Set());
32-
const [disconnectionAlert, setDisconnectionAlert] = useState<string | null>(null);
33+
const [connectedClients, setConnectedClients] = useState<Set<number>>(
34+
new Set(),
35+
);
36+
const [disconnectionAlert, setDisconnectionAlert] = useState<string | null>(
37+
null,
38+
);
3339

3440
// Layout states
3541
const [leftWidth, setLeftWidth] = useState(50);
3642
const isDragging = useRef(false);
3743
const containerRef = useRef<HTMLDivElement>(null);
3844
const providerRef = useRef<WebsocketProvider | null>(null);
3945
const bindingRef = useRef<MonacoBinding | null>(null);
40-
const editorRef = React.useRef<MonacoEditor.IStandaloneCodeEditor | null>(null);
41-
const sockServerURI = process.env.SOCK_SERVER_URL || "ws://localhost:4444";
46+
const editorRef = React.useRef<MonacoEditor.IStandaloneCodeEditor | null>(
47+
null,
48+
);
49+
const sockServerURI = process.env.SOCK_SERVER_URL || 'ws://localhost:4444';
4250

4351
// Handle dragging of the divider
4452
const handleMouseDown = useCallback(() => {
@@ -68,21 +76,21 @@ const CollaborationPage = () => {
6876

6977
const handleEditorMount = (editor: any, monaco: any) => {
7078
if (!matchId) {
71-
console.error("Cannot mount editor: Match ID is undefined");
79+
console.error('Cannot mount editor: Match ID is undefined');
7280
return;
7381
}
7482
editorRef.current = editor;
7583
const doc = new Y.Doc();
7684
providerRef.current = new WebsocketProvider(sockServerURI, matchId, doc);
77-
const type = doc.getText("monaco");
85+
const type = doc.getText('monaco');
7886

7987
// Set up awareness handling
8088
providerRef.current.awareness.setLocalState({
8189
client: Math.floor(Math.random() * 1000000), // Generate random client ID
8290
user: {
8391
name: `User ${Math.floor(Math.random() * 100)}`, // You can replace this with actual user info
84-
color: `#${Math.floor(Math.random()*16777215).toString(16)}` // Random color
85-
}
92+
color: `#${Math.floor(Math.random() * 16777215).toString(16)}`, // Random color
93+
},
8694
});
8795

8896
// Handle client updates
@@ -111,13 +119,13 @@ const CollaborationPage = () => {
111119
const model = editorRef.current?.getModel();
112120
if (editorRef.current && model) {
113121
bindingRef.current = new MonacoBinding(
114-
type,
115-
model,
116-
new Set([editorRef.current]),
117-
providerRef.current.awareness
122+
type,
123+
model,
124+
new Set([editorRef.current]),
125+
providerRef.current.awareness,
118126
);
119127
} else {
120-
console.error("Monaco editor model is null");
128+
console.error('Monaco editor model is null');
121129
}
122130
};
123131

@@ -212,12 +220,10 @@ const CollaborationPage = () => {
212220
</div>
213221
</div>
214222

215-
<ProblemCodeEditor
216-
onMount={handleEditorMount}
217-
/>
223+
<ProblemCodeEditor onMount={handleEditorMount} />
218224
</div>
219225
</div>
220226
);
221227
};
222228

223-
export default CollaborationPage;
229+
export default CollaborationPage;

peerprep-fe/src/app/match/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function LoadingPage() {
2424
if (lastMessage.status === 'matched') {
2525
console.log('Match found, your partner is', lastMessage.match.name);
2626
setMatchStatus('matched');
27-
const matchId = lastMessage.matchId;
27+
const matchId = lastMessage.matchId;
2828
setTimeout(() => {
2929
router.push(`/collaboration?matchId=${matchId}`);
3030
}, 4000);
Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
1-
import React from "react";
2-
import Editor from "@monaco-editor/react";
1+
import React from 'react';
2+
import Editor from '@monaco-editor/react';
33

44
interface ProblemCodeEditorProps {
5-
onMount: (editor: any, monaco: any) => void;
5+
onMount: (editor: any, monaco: any) => void;
66
}
77

8-
const ProblemCodeEditor: React.FC<ProblemCodeEditorProps> = ({
9-
onMount,
10-
}) => {
11-
12-
return (
13-
<div
14-
style={{
15-
borderRadius: "12px",
16-
overflow: "hidden",
17-
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.5)",
18-
}}
19-
className="flex-grow overflow-y-auto bg-gray-900 font-mono text-gray-100"
20-
>
21-
<Editor
22-
height="100%"
23-
width="100%"
24-
language="python"
25-
theme="vs-dark"
26-
onMount={onMount}
27-
options={{
28-
fontSize: 14,
29-
minimap: { enabled: false },
30-
lineNumbers: "on",
31-
wordWrap: "on",
32-
}}
33-
/>
34-
</div>
35-
);
8+
const ProblemCodeEditor: React.FC<ProblemCodeEditorProps> = ({ onMount }) => {
9+
return (
10+
<div
11+
style={{
12+
borderRadius: '12px',
13+
overflow: 'hidden',
14+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.5)',
15+
}}
16+
className="flex-grow overflow-y-auto bg-gray-900 font-mono text-gray-100"
17+
>
18+
<Editor
19+
height="100%"
20+
width="100%"
21+
language="python"
22+
theme="vs-dark"
23+
onMount={onMount}
24+
options={{
25+
fontSize: 14,
26+
minimap: { enabled: false },
27+
lineNumbers: 'on',
28+
wordWrap: 'on',
29+
}}
30+
/>
31+
</div>
32+
);
3633
};
3734

3835
export default ProblemCodeEditor;

0 commit comments

Comments
 (0)