Skip to content

Commit 3502e42

Browse files
committed
Sync submission across webrtc and update history-service routing
1 parent cab2b2c commit 3502e42

File tree

11 files changed

+314
-84
lines changed

11 files changed

+314
-84
lines changed

apps/frontend/src/app/collaboration/[id]/page.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { Content } from "antd/es/layout/layout";
1717
import "./styles.scss";
1818
import { useRouter, useSearchParams } from "next/navigation";
19-
import { useEffect, useState } from "react";
19+
import { useEffect, useRef, useState } from "react";
2020
import { GetSingleQuestion, Question } from "@/app/services/question";
2121
import {
2222
ClockCircleOutlined,
@@ -28,13 +28,15 @@ import {
2828
} from "@ant-design/icons";
2929
import { ProgrammingLanguageOptions } from "@/utils/SelectOptions";
3030
import CollaborativeEditor from "@/components/CollaborativeEditor/CollaborativeEditor";
31-
import { CreateHistory, UpdateHistory } from "@/app/services/history";
31+
import { CreateOrUpdateHistory } from "@/app/services/history";
3232
import { Language } from "@codemirror/language";
33+
import { WebrtcProvider } from "y-webrtc";
3334

3435
interface CollaborationProps {}
3536

3637
export default function CollaborationPage(props: CollaborationProps) {
3738
const router = useRouter();
39+
const providerRef = useRef<WebrtcProvider | null>(null);
3840

3941
const [isLoading, setIsLoading] = useState<boolean>(false);
4042

@@ -80,26 +82,18 @@ export default function CollaborationPage(props: CollaborationProps) {
8082
});
8183
};
8284

83-
const handleSubmitCode = async () => {
84-
if (!historyDocRefId) {
85-
const data = await CreateHistory({
86-
title: questionTitle ?? "",
87-
code: code,
88-
language: selectedLanguage,
89-
user: currentUser ?? "",
90-
matchedUser: matchedUser ?? "",
91-
matchId: collaborationId ?? "",
92-
matchedTopics: matchedTopics ?? [],
93-
questionDocRefId: questionDocRefId ?? "",
94-
questionDifficulty: complexity ?? "",
95-
questionTopics: categories,
96-
});
97-
setHistoryDocRefId(data.docRefId);
98-
successMessage("Code submitted successfully!");
99-
return;
85+
const sendCodeSavedStatusToMatchedUser = () => {
86+
if (!providerRef.current) {
87+
throw new Error("Provider not initialized");
10088
}
89+
providerRef.current.awareness.setLocalStateField("codeSavedStatus", true);
90+
}
10191

102-
UpdateHistory({
92+
const handleSubmitCode = async () => {
93+
if (!collaborationId) {
94+
throw new Error("Collaboration ID not found");
95+
}
96+
const data = await CreateOrUpdateHistory({
10397
title: questionTitle ?? "",
10498
code: code,
10599
language: selectedLanguage,
@@ -110,8 +104,9 @@ export default function CollaborationPage(props: CollaborationProps) {
110104
questionDocRefId: questionDocRefId ?? "",
111105
questionDifficulty: complexity ?? "",
112106
questionTopics: categories,
113-
}, historyDocRefId!);
114-
successMessage("Code updated successfully!");
107+
}, collaborationId);
108+
successMessage("Code saved successfully!");
109+
sendCodeSavedStatusToMatchedUser();
115110
}
116111

117112
const handleCodeChange = (code: string) => {
@@ -273,7 +268,9 @@ export default function CollaborationPage(props: CollaborationProps) {
273268
user={currentUser}
274269
collaborationId={collaborationId}
275270
language={selectedLanguage}
276-
onCodeChange={handleCodeChange}
271+
providerRef={providerRef}
272+
matchedUser={matchedUser}
273+
onCodeChange={handleCodeChange}
277274
/>
278275
)}
279276
</div>

apps/frontend/src/app/services/history.ts

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,72 @@ export interface History {
1313
questionTopics: string[];
1414
createdAt?: string;
1515
updatedAt?: string;
16-
docRefId?: string;
1716
}
1817

19-
export const CreateHistory = async (
20-
history: History
18+
export const CreateOrUpdateHistory = async (
19+
history: History,
20+
matchId: string,
2121
): Promise<History> => {
22-
const response = await fetch(`${HISTORY_SERVICE_URL}histories`, {
23-
method: "POST",
24-
headers: {
25-
"Content-Type": "application/json",
26-
},
27-
body: JSON.stringify(history),
28-
});
22+
const response = await fetch(
23+
`${HISTORY_SERVICE_URL}histories/${matchId}`,
24+
{
25+
method: "PUT",
26+
headers: {
27+
"Content-Type": "application/json",
28+
},
29+
body: JSON.stringify(history),
30+
}
31+
);
2932

3033
if (response.status === 200) {
3134
return response.json();
3235
} else {
3336
throw new Error(
34-
`Error creating history: ${response.status} ${response.statusText}`
37+
`Error saving history: ${response.status} ${response.statusText}`
3538
);
3639
}
37-
};
40+
}
3841

39-
export const UpdateHistory = async (
40-
history: History,
41-
historyDocRefId: string
42+
export const GetHistory = async (
43+
matchId: string,
4244
): Promise<History> => {
4345
const response = await fetch(
44-
`${HISTORY_SERVICE_URL}histories/${historyDocRefId}`,
46+
`${HISTORY_SERVICE_URL}histories/${matchId}`,
4547
{
46-
method: "PUT",
48+
method: "GET",
49+
headers: {
50+
"Content-Type": "application/json",
51+
},
52+
}
53+
);
54+
55+
if (response.status === 200) {
56+
return response.json();
57+
} else {
58+
throw new Error(
59+
`Error reading history: ${response.status} ${response.statusText}`
60+
);
61+
}
62+
}
63+
64+
export const GetUserHistories = async (
65+
username: string,
66+
): Promise<History[]> => {
67+
const response = await fetch(
68+
`${HISTORY_SERVICE_URL}histories/${username}`,
69+
{
70+
method: "GET",
4771
headers: {
4872
"Content-Type": "application/json",
4973
},
50-
body: JSON.stringify(history),
5174
}
5275
);
5376

5477
if (response.status === 200) {
5578
return response.json();
5679
} else {
5780
throw new Error(
58-
`Error updating history: ${response.status} ${response.statusText}`
81+
`Error reading user histories: ${response.status} ${response.statusText}`
5982
);
6083
}
6184
}

apps/frontend/src/components/CollaborativeEditor/CollaborativeEditor.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Referenced from example in https://www.npmjs.com/package/y-codemirror.next
2-
import React, { useEffect, useRef, useState } from "react";
2+
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
33
import * as Y from "yjs";
44
import { yCollab } from "y-codemirror.next";
55
import { WebrtcProvider } from "y-webrtc";
@@ -19,9 +19,26 @@ interface CollaborativeEditorProps {
1919
user: string;
2020
collaborationId: string;
2121
language: string;
22+
providerRef: MutableRefObject<WebrtcProvider | null>;
23+
matchedUser: string | undefined;
2224
onCodeChange: (code: string) => void;
2325
}
2426

27+
interface AwarenessUpdate {
28+
added: number[];
29+
updated: number[];
30+
removed: number[];
31+
}
32+
33+
interface Awareness {
34+
user: {
35+
name: string;
36+
color: string;
37+
colorLight: string;
38+
};
39+
codeSavedStatus: boolean;
40+
}
41+
2542
export const usercolors = [
2643
{ color: "#30bced", light: "#30bced33" },
2744
{ color: "#6eeb83", light: "#6eeb8333" },
@@ -154,8 +171,8 @@ const CollaborativeEditor = (props: CollaborativeEditorProps) => {
154171
const provider = new WebrtcProvider(props.collaborationId, ydoc, {
155172
signaling: [process.env.NEXT_PUBLIC_SIGNALLING_SERVICE_URL],
156173
});
174+
props.providerRef.current = provider;
157175
const ytext = ydoc.getText("codemirror");
158-
console.log("testing y text", ytext); // TODO: remove
159176
const undoManager = new Y.UndoManager(ytext);
160177

161178
provider.awareness.setLocalStateField("user", {
@@ -164,6 +181,20 @@ const CollaborativeEditor = (props: CollaborativeEditorProps) => {
164181
colorLight: userColor.light,
165182
});
166183

184+
// Listener for awareness updates to receive status changes from peers
185+
provider.awareness.on("update", ({ added, updated } : AwarenessUpdate) => {
186+
added.concat(updated).filter(clientId => clientId !== provider.awareness.clientID).forEach((clientID) => {
187+
const state = provider.awareness.getStates().get(clientID) as Awareness;
188+
if (state && state.codeSavedStatus) {
189+
// Display the received status message
190+
messageApi.open({
191+
type: "success",
192+
content: `${props.matchedUser ?? "Peer"} saved code successfully!`,
193+
});
194+
}
195+
});
196+
});
197+
167198
const state = EditorState.create({
168199
doc: ytext.toString(),
169200
extensions: [

apps/history-service/handlers/create.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handlers
22

33
import (
4+
"cloud.google.com/go/firestore"
45
"encoding/json"
56
"google.golang.org/api/iterator"
67
"history-service/models"
@@ -10,40 +11,36 @@ import (
1011

1112
// Create a new code snippet
1213
func (s *Service) CreateHistory(w http.ResponseWriter, r *http.Request) {
13-
println("test1")
1414
ctx := r.Context()
1515

1616
// Parse request
1717
var collaborationHistory models.CollaborationHistory
1818
if err := utils.DecodeJSONBody(w, r, &collaborationHistory); err != nil {
1919
http.Error(w, err.Error(), http.StatusBadRequest)
20-
println(err.Error())
2120
return
2221
}
2322

24-
println("test2")
23+
// Document reference ID in firestore mapped to the match ID in model
24+
docRef := s.Client.Collection("collaboration-history").Doc(collaborationHistory.MatchID)
2525

26-
docRef, _, err := s.Client.Collection("collaboration-history").Add(ctx, map[string]interface{}{
26+
_, err := docRef.Set(ctx, map[string]interface{}{
2727
"title": collaborationHistory.Title,
2828
"code": collaborationHistory.Code,
2929
"language": collaborationHistory.Language,
3030
"user": collaborationHistory.User,
3131
"matchedUser": collaborationHistory.MatchedUser,
32-
"matchId": collaborationHistory.MatchID,
3332
"matchedTopics": collaborationHistory.MatchedTopics,
3433
"questionDocRefId": collaborationHistory.QuestionDocRefID,
3534
"questionDifficulty": collaborationHistory.QuestionDifficulty,
3635
"questionTopics": collaborationHistory.QuestionTopics,
37-
"createdAt": collaborationHistory.CreatedAt,
38-
"updatedAt": collaborationHistory.UpdatedAt,
36+
"createdAt": firestore.ServerTimestamp,
37+
"updatedAt": firestore.ServerTimestamp,
3938
})
4039
if err != nil {
4140
http.Error(w, err.Error(), http.StatusInternalServerError)
4241
return
4342
}
4443

45-
println("test3")
46-
4744
// Get data
4845
doc, err := docRef.Get(ctx)
4946
if err != nil {
@@ -55,16 +52,12 @@ func (s *Service) CreateHistory(w http.ResponseWriter, r *http.Request) {
5552
return
5653
}
5754

58-
println("test4")
59-
6055
// Map data
6156
if err := doc.DataTo(&collaborationHistory); err != nil {
6257
http.Error(w, "Failed to map history data", http.StatusInternalServerError)
6358
return
6459
}
65-
collaborationHistory.DocRefID = doc.Ref.ID
66-
67-
println(collaborationHistory.Title, "test")
60+
collaborationHistory.MatchID = doc.Ref.ID
6861

6962
w.Header().Set("Content-Type", "application/json")
7063
w.WriteHeader(http.StatusOK)

0 commit comments

Comments
 (0)