Skip to content

Commit

Permalink
Sync submission across webrtc and update history-service routing
Browse files Browse the repository at this point in the history
  • Loading branch information
solomonng2001 committed Oct 28, 2024
1 parent cab2b2c commit 3502e42
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 84 deletions.
43 changes: 20 additions & 23 deletions apps/frontend/src/app/collaboration/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { Content } from "antd/es/layout/layout";
import "./styles.scss";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { GetSingleQuestion, Question } from "@/app/services/question";
import {
ClockCircleOutlined,
Expand All @@ -28,13 +28,15 @@ import {
} from "@ant-design/icons";
import { ProgrammingLanguageOptions } from "@/utils/SelectOptions";
import CollaborativeEditor from "@/components/CollaborativeEditor/CollaborativeEditor";
import { CreateHistory, UpdateHistory } from "@/app/services/history";
import { CreateOrUpdateHistory } from "@/app/services/history";
import { Language } from "@codemirror/language";
import { WebrtcProvider } from "y-webrtc";

interface CollaborationProps {}

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

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

Expand Down Expand Up @@ -80,26 +82,18 @@ export default function CollaborationPage(props: CollaborationProps) {
});
};

const handleSubmitCode = async () => {
if (!historyDocRefId) {
const data = await CreateHistory({
title: questionTitle ?? "",
code: code,
language: selectedLanguage,
user: currentUser ?? "",
matchedUser: matchedUser ?? "",
matchId: collaborationId ?? "",
matchedTopics: matchedTopics ?? [],
questionDocRefId: questionDocRefId ?? "",
questionDifficulty: complexity ?? "",
questionTopics: categories,
});
setHistoryDocRefId(data.docRefId);
successMessage("Code submitted successfully!");
return;
const sendCodeSavedStatusToMatchedUser = () => {
if (!providerRef.current) {
throw new Error("Provider not initialized");
}
providerRef.current.awareness.setLocalStateField("codeSavedStatus", true);
}

UpdateHistory({
const handleSubmitCode = async () => {
if (!collaborationId) {
throw new Error("Collaboration ID not found");
}
const data = await CreateOrUpdateHistory({
title: questionTitle ?? "",
code: code,
language: selectedLanguage,
Expand All @@ -110,8 +104,9 @@ export default function CollaborationPage(props: CollaborationProps) {
questionDocRefId: questionDocRefId ?? "",
questionDifficulty: complexity ?? "",
questionTopics: categories,
}, historyDocRefId!);
successMessage("Code updated successfully!");
}, collaborationId);
successMessage("Code saved successfully!");
sendCodeSavedStatusToMatchedUser();
}

const handleCodeChange = (code: string) => {
Expand Down Expand Up @@ -273,7 +268,9 @@ export default function CollaborationPage(props: CollaborationProps) {
user={currentUser}
collaborationId={collaborationId}
language={selectedLanguage}
onCodeChange={handleCodeChange}
providerRef={providerRef}
matchedUser={matchedUser}
onCodeChange={handleCodeChange}
/>
)}
</div>
Expand Down
61 changes: 42 additions & 19 deletions apps/frontend/src/app/services/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,72 @@ export interface History {
questionTopics: string[];
createdAt?: string;
updatedAt?: string;
docRefId?: string;
}

export const CreateHistory = async (
history: History
export const CreateOrUpdateHistory = async (
history: History,
matchId: string,
): Promise<History> => {
const response = await fetch(`${HISTORY_SERVICE_URL}histories`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(history),
});
const response = await fetch(
`${HISTORY_SERVICE_URL}histories/${matchId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(history),
}
);

if (response.status === 200) {
return response.json();
} else {
throw new Error(
`Error creating history: ${response.status} ${response.statusText}`
`Error saving history: ${response.status} ${response.statusText}`
);
}
};
}

export const UpdateHistory = async (
history: History,
historyDocRefId: string
export const GetHistory = async (
matchId: string,
): Promise<History> => {
const response = await fetch(
`${HISTORY_SERVICE_URL}histories/${historyDocRefId}`,
`${HISTORY_SERVICE_URL}histories/${matchId}`,
{
method: "PUT",
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);

if (response.status === 200) {
return response.json();
} else {
throw new Error(
`Error reading history: ${response.status} ${response.statusText}`
);
}
}

export const GetUserHistories = async (
username: string,
): Promise<History[]> => {
const response = await fetch(
`${HISTORY_SERVICE_URL}histories/${username}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(history),
}
);

if (response.status === 200) {
return response.json();
} else {
throw new Error(
`Error updating history: ${response.status} ${response.statusText}`
`Error reading user histories: ${response.status} ${response.statusText}`
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Referenced from example in https://www.npmjs.com/package/y-codemirror.next
import React, { useEffect, useRef, useState } from "react";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import * as Y from "yjs";
import { yCollab } from "y-codemirror.next";
import { WebrtcProvider } from "y-webrtc";
Expand All @@ -19,9 +19,26 @@ interface CollaborativeEditorProps {
user: string;
collaborationId: string;
language: string;
providerRef: MutableRefObject<WebrtcProvider | null>;
matchedUser: string | undefined;
onCodeChange: (code: string) => void;
}

interface AwarenessUpdate {
added: number[];
updated: number[];
removed: number[];
}

interface Awareness {
user: {
name: string;
color: string;
colorLight: string;
};
codeSavedStatus: boolean;
}

export const usercolors = [
{ color: "#30bced", light: "#30bced33" },
{ color: "#6eeb83", light: "#6eeb8333" },
Expand Down Expand Up @@ -154,8 +171,8 @@ const CollaborativeEditor = (props: CollaborativeEditorProps) => {
const provider = new WebrtcProvider(props.collaborationId, ydoc, {
signaling: [process.env.NEXT_PUBLIC_SIGNALLING_SERVICE_URL],
});
props.providerRef.current = provider;
const ytext = ydoc.getText("codemirror");
console.log("testing y text", ytext); // TODO: remove
const undoManager = new Y.UndoManager(ytext);

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

// Listener for awareness updates to receive status changes from peers
provider.awareness.on("update", ({ added, updated } : AwarenessUpdate) => {
added.concat(updated).filter(clientId => clientId !== provider.awareness.clientID).forEach((clientID) => {
const state = provider.awareness.getStates().get(clientID) as Awareness;
if (state && state.codeSavedStatus) {
// Display the received status message
messageApi.open({
type: "success",
content: `${props.matchedUser ?? "Peer"} saved code successfully!`,
});
}
});
});

const state = EditorState.create({
doc: ytext.toString(),
extensions: [
Expand Down
21 changes: 7 additions & 14 deletions apps/history-service/handlers/create.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"cloud.google.com/go/firestore"
"encoding/json"
"google.golang.org/api/iterator"
"history-service/models"
Expand All @@ -10,40 +11,36 @@ import (

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

// Parse request
var collaborationHistory models.CollaborationHistory
if err := utils.DecodeJSONBody(w, r, &collaborationHistory); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
println(err.Error())
return
}

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

docRef, _, err := s.Client.Collection("collaboration-history").Add(ctx, map[string]interface{}{
_, err := docRef.Set(ctx, map[string]interface{}{
"title": collaborationHistory.Title,
"code": collaborationHistory.Code,
"language": collaborationHistory.Language,
"user": collaborationHistory.User,
"matchedUser": collaborationHistory.MatchedUser,
"matchId": collaborationHistory.MatchID,
"matchedTopics": collaborationHistory.MatchedTopics,
"questionDocRefId": collaborationHistory.QuestionDocRefID,
"questionDifficulty": collaborationHistory.QuestionDifficulty,
"questionTopics": collaborationHistory.QuestionTopics,
"createdAt": collaborationHistory.CreatedAt,
"updatedAt": collaborationHistory.UpdatedAt,
"createdAt": firestore.ServerTimestamp,
"updatedAt": firestore.ServerTimestamp,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

println("test3")

// Get data
doc, err := docRef.Get(ctx)
if err != nil {
Expand All @@ -55,16 +52,12 @@ func (s *Service) CreateHistory(w http.ResponseWriter, r *http.Request) {
return
}

println("test4")

// Map data
if err := doc.DataTo(&collaborationHistory); err != nil {
http.Error(w, "Failed to map history data", http.StatusInternalServerError)
return
}
collaborationHistory.DocRefID = doc.Ref.ID

println(collaborationHistory.Title, "test")
collaborationHistory.MatchID = doc.Ref.ID

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
Expand Down
Loading

0 comments on commit 3502e42

Please sign in to comment.