Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/controllers/add_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ func AddTaskHandler(w http.ResponseWriter, r *http.Request) {

logStore := models.GetLogStore()
job := Job{
Name: "Add Task",
Name: "Add Task",
ClientID: requestBody.UUID,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("Adding task: %s", requestBody.Description), requestBody.UUID, "Add Task")
err := tw.AddTaskToTaskwarrior(requestBody, dueDateStr)
Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/complete_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ func CompleteTaskHandler(w http.ResponseWriter, r *http.Request) {
// }
logStore := models.GetLogStore()
job := Job{
Name: "Complete Task",
Name: "Complete Task",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("Completing task UUID: %s", taskuuid), uuid, "Complete Task")
err := tw.CompleteTaskInTaskwarrior(email, encryptionSecret, uuid, taskuuid)
Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/complete_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func BulkCompleteTaskHandler(w http.ResponseWriter, r *http.Request) {
logStore := models.GetLogStore()

job := Job{
Name: "Bulk Complete Tasks",
Name: "Bulk Complete Tasks",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("[Bulk Complete] Starting %d tasks", len(taskUUIDs)), uuid, "Bulk Complete Task")

Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/delete_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func DeleteTaskHandler(w http.ResponseWriter, r *http.Request) {

logStore := models.GetLogStore()
job := Job{
Name: "Delete Task",
Name: "Delete Task",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("Deleting task UUID: %s", taskuuid), uuid, "Delete Task")
err := tw.DeleteTaskInTaskwarrior(email, encryptionSecret, uuid, taskuuid)
Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/delete_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func BulkDeleteTaskHandler(w http.ResponseWriter, r *http.Request) {
logStore := models.GetLogStore()

job := Job{
Name: "Bulk Delete Tasks",
Name: "Bulk Delete Tasks",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("[Bulk Delete] Starting %d tasks", len(taskUUIDs)), uuid, "Bulk Delete Task")

Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/edit_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) {

logStore := models.GetLogStore()
job := Job{
Name: "Edit Task",
Name: "Edit Task",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("Editing task UUID: %s", taskUUID), uuid, "Edit Task")

Expand Down
25 changes: 15 additions & 10 deletions backend/controllers/job_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
)

type Job struct {
Name string
Execute func() error
Name string
ClientID string
Execute func() error
}

type JobQueue struct {
Expand All @@ -28,28 +29,32 @@ func (q *JobQueue) AddJob(job Job) {

// notify job queued
go BroadcastJobStatus(JobStatus{
Job: job.Name,
Status: "queued",
Job: job.Name,
Status: "queued",
ClientID: job.ClientID,
})
}

func (q *JobQueue) processJobs() {
for job := range q.jobChannel {
go BroadcastJobStatus(JobStatus{
Job: job.Name,
Status: "in-progress",
Job: job.Name,
Status: "in-progress",
ClientID: job.ClientID,
})

if err := job.Execute(); err != nil {
go BroadcastJobStatus(JobStatus{
Job: job.Name,
Status: "failure",
Job: job.Name,
Status: "failure",
ClientID: job.ClientID,
})
} else {
// utils.Logger.Infof("Success in executing job %s", job.Name)
go BroadcastJobStatus(JobStatus{
Job: job.Name,
Status: "success",
Job: job.Name,
Status: "success",
ClientID: job.ClientID,
})
}

Expand Down
3 changes: 2 additions & 1 deletion backend/controllers/modify_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ func ModifyTaskHandler(w http.ResponseWriter, r *http.Request) {

logStore := models.GetLogStore()
job := Job{
Name: "Modify Task",
Name: "Modify Task",
ClientID: uuid,
Execute: func() error {
logStore.AddLog("INFO", fmt.Sprintf("Modifying task UUID: %s", taskUUID), uuid, "Modify Task")
err := tw.ModifyTaskInTaskwarrior(uuid, description, project, priority, status, due, email, encryptionSecret, taskUUID, tags, depends)
Expand Down
68 changes: 54 additions & 14 deletions backend/controllers/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"
"os"
"strings"
"sync"

"ccsync_backend/utils"

Expand All @@ -22,8 +23,9 @@ func getEnv() string {
}

type JobStatus struct {
Job string `json:"job"`
Status string `json:"status"`
Job string `json:"job"`
Status string `json:"status"`
ClientID string `json:"clientID,omitempty"`
}

// checkWebSocketOrigin validates the Origin header against allowed origins
Expand Down Expand Up @@ -80,7 +82,9 @@ var upgrader = websocket.Upgrader{
CheckOrigin: checkWebSocketOrigin,
}

var clients = make(map[*websocket.Conn]bool)
// clients maps each WebSocket to the authenticated user's UUID.
var clients = make(map[*websocket.Conn]string)
var clientsMu sync.RWMutex
var broadcast = make(chan JobStatus)

// AuthenticatedWebSocketHandler creates a WebSocket handler that requires authentication
Expand All @@ -101,6 +105,15 @@ func AuthenticatedWebSocketHandler(store *sessions.CookieStore) http.HandlerFunc
return
}

// Prefer the session UUID over the clientID query param so a client
// cannot subscribe to another user's job-status events.
userUUID, _ := userInfo["uuid"].(string)
if userUUID == "" {
utils.Logger.Warnf("WebSocket auth failed: session user missing uuid")
http.Error(w, "Authentication required", http.StatusUnauthorized)
return
}

// User is authenticated, proceed with WebSocket upgrade
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
Expand All @@ -109,11 +122,18 @@ func AuthenticatedWebSocketHandler(store *sessions.CookieStore) http.HandlerFunc
}
defer ws.Close()

clients[ws] = true
clientsMu.Lock()
clients[ws] = userUUID
clientsMu.Unlock()
defer func() {
clientsMu.Lock()
delete(clients, ws)
clientsMu.Unlock()
}()

for {
_, _, err := ws.ReadMessage()
if err != nil {
delete(clients, ws)
break
}
}
Expand All @@ -130,11 +150,20 @@ func WebSocketHandler(w http.ResponseWriter, r *http.Request) {
}
defer ws.Close()

clients[ws] = true
// Unauthenticated legacy handler: register with an empty id so scoped
// job-status events (which always carry a ClientID) are not delivered here.
clientsMu.Lock()
clients[ws] = ""
clientsMu.Unlock()
defer func() {
clientsMu.Lock()
delete(clients, ws)
clientsMu.Unlock()
}()

for {
_, _, err := ws.ReadMessage()
if err != nil {
delete(clients, ws)
break
}
}
Expand All @@ -144,15 +173,26 @@ func BroadcastJobStatus(jobStatus JobStatus) {
broadcast <- jobStatus
}

// deliverJobStatus sends a job-status event only to WebSockets registered
// for the same ClientID as the job owner.
func deliverJobStatus(jobStatus JobStatus) {
clientsMu.Lock()
defer clientsMu.Unlock()

for client, clientID := range clients {
if jobStatus.ClientID == "" || clientID != jobStatus.ClientID {
continue
}
if err := client.WriteJSON(jobStatus); err != nil {
client.Close()
delete(clients, client)
}
}
}

func JobStatusManager() {
for {
jobStatus := <-broadcast
for client := range clients {
err := client.WriteJSON(jobStatus)
if err != nil {
client.Close()
delete(clients, client)
}
}
deliverJobStatus(jobStatus)
}
}
Loading
Loading