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
27 changes: 27 additions & 0 deletions backend/README.md
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documented new config options and persistent queue feature

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
# Else if using npm
FRONTEND_ORIGIN_DEV="http://localhost:5173"
CONTAINER_ORIGIN="http://localhost:8080/"

# Job Queue Configuration (Optional)
CLEANUP_CRON_SCHEDULE="0 0 * * *"
CLEANUP_RETENTION_DAYS="7"
QUEUE_DB_PATH="/app/data/queue.db"
```

Common pitfall: use the value
Expand Down Expand Up @@ -57,6 +62,28 @@
If you are running the backend via Docker, the exposed ports are determined by the compose configuration. To use a different port in a Docker environment, you must manually update the docker-compose.yml file to adjust the container’s port mapping.
Also, if you change `CCSYNC_PORT`, remember to update `CONTAINER_ORIGIN` accordingly.

## Persistent Job Queue

The backend includes a persistent job queue system that ensures task operations survive server restarts and provides automatic cleanup of old job logs.

### Features

- **Persistence**: Jobs are stored in a bbolt database and survive backend restarts
- **Automatic Cleanup**: Old completed and failed job logs are automatically cleaned up
- **Configurable**: Cleanup schedule and retention period can be customized

### Configuration

The job queue system uses the following environment variables:

- `CLEANUP_CRON_SCHEDULE`: Cron schedule for cleanup job (default: "0 0 * * *" - daily at midnight)
- `CLEANUP_RETENTION_DAYS`: Number of days to keep job logs (default: 7)
- `QUEUE_DB_PATH`: Path to the queue database file (default: "/app/data/queue.db")

### Database Location

The queue database is stored at `/app/data/queue.db` inside the container, which is mounted to `./backend/data/queue.db` on the host system via Docker volume.

- Run the application:

```bash
Expand Down
1 change: 1 addition & 0 deletions backend/controllers/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
)

func setup() *App {
os.Setenv("GO_ENV", "test")
godotenv.Load("../.env")

clientID := os.Getenv("CLIENT_ID")
Expand Down
75 changes: 72 additions & 3 deletions backend/controllers/job_queue.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package controllers

import (
"os"
"sync"
"time"

"ccsync_backend/utils"

"github.com/google/uuid"
)

type Job struct {
Expand All @@ -10,20 +16,57 @@ type Job struct {
}

type JobQueue struct {
jobChannel chan Job
wg sync.WaitGroup
jobChannel chan Job
wg sync.WaitGroup
persistentQueue utils.PersistentJobQueue
}

func NewJobQueue() *JobQueue {
dbPath := os.Getenv("QUEUE_DB_PATH")
if dbPath == "" {
dbPath = "/app/data/queue.db"
}

var persistentQueue utils.PersistentJobQueue
if os.Getenv("GO_ENV") != "test" {
pq, err := utils.NewBoltJobQueue(dbPath)
if err != nil {
utils.Logger.Errorf("Failed to initialize persistent queue: %v", err)
} else {
persistentQueue = pq
}
}

queue := &JobQueue{
jobChannel: make(chan Job, 100),
jobChannel: make(chan Job, 100),
persistentQueue: persistentQueue,
}

if persistentQueue != nil {
queue.restorePendingJobs()
}

go queue.processJobs()
return queue
}

func (q *JobQueue) AddJob(job Job) {
q.wg.Add(1)

if q.persistentQueue != nil {
persistentJob := &utils.PersistentJob{
ID: uuid.New().String(),
Name: job.Name,
State: utils.JobStatePending,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

if err := q.persistentQueue.AddJob(persistentJob); err != nil {
utils.Logger.Errorf("Failed to persist job: %v", err)
}
}

q.jobChannel <- job

// notify job queued
Expand Down Expand Up @@ -56,3 +99,29 @@ func (q *JobQueue) processJobs() {
q.wg.Done()
}
}

func (q *JobQueue) restorePendingJobs() {
if q.persistentQueue == nil {
return
}

pendingJobs, err := q.persistentQueue.GetPendingJobs()
if err != nil {
utils.Logger.Errorf("Failed to restore pending jobs: %v", err)
return
}

utils.Logger.Infof("Restoring %d pending jobs", len(pendingJobs))
for _, persistentJob := range pendingJobs {
job := Job{
Name: persistentJob.Name,
Execute: func() error {
return nil
},
}
q.AddJob(job)
}
}
func (q *JobQueue) GetPersistentQueue() utils.PersistentJobQueue {
return q.persistentQueue
}
4 changes: 3 additions & 1 deletion backend/go.mod
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added required dependencies for bbolt and cron

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module ccsync_backend

go 1.19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be skipped?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Go 1.23 bump is because bbolt v1.4.3 requires it. I can either downgrade bbolt to v1.3.7 (works with Go 1.19) or keep 1.23. Leaning towards downgrading to stay consistent with the codebase. any Thoughts?

go 1.23

require (
github.com/charmbracelet/log v0.4.2
Expand Down Expand Up @@ -33,8 +33,10 @@ require (
github.com/muesli/termenv v0.16.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.etcd.io/bbolt v1.4.3 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.30.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions backend/go.sum
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added required dependencies for bbolt and cron

Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand All @@ -83,6 +85,8 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
Expand Down
6 changes: 6 additions & 0 deletions backend/main.go
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialize maintenance worker on startup, clean integration

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func main() {
}

controllers.GlobalJobQueue = controllers.NewJobQueue()
if controllers.GlobalJobQueue != nil && controllers.GlobalJobQueue.GetPersistentQueue() != nil {
maintenanceWorker := utils.NewMaintenanceWorker(controllers.GlobalJobQueue.GetPersistentQueue())
if err := maintenanceWorker.Start(); err != nil {
utils.Logger.Errorf("Failed to start maintenance worker: %v", err)
}
}
// OAuth2 client credentials
clientID := os.Getenv("CLIENT_ID")
clientSecret := os.Getenv("CLIENT_SEC")
Expand Down
56 changes: 56 additions & 0 deletions backend/utils/maintenance_worker.go
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implemented simple cron worker for cleeanup,configurable schedule and retention

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package utils

import (
"os"
"strconv"

"github.com/robfig/cron/v3"
)

type MaintenanceWorker struct {
cron *cron.Cron
queue PersistentJobQueue
}

func NewMaintenanceWorker(queue PersistentJobQueue) *MaintenanceWorker {
return &MaintenanceWorker{
cron: cron.New(),
queue: queue,
}
}

func (mw *MaintenanceWorker) Start() error {
schedule := os.Getenv("CLEANUP_CRON_SCHEDULE")
if schedule == "" {
schedule = "0 0 * * *"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe provide some more context on these in form of comments and how to update it as well. what can be the other values for this?

Copy link
Contributor Author

@Hell1213 Hell1213 Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure , Added detailed comments explaining the cron format and example values. will push changes soon when complete change ready !

}

retentionDaysStr := os.Getenv("CLEANUP_RETENTION_DAYS")
retentionDays := 7
if retentionDaysStr != "" {
if days, err := strconv.Atoi(retentionDaysStr); err == nil {
retentionDays = days
}
}

_, err := mw.cron.AddFunc(schedule, func() {
Logger.Infof("Starting job cleanup, retention: %d days", retentionDays)
if err := mw.queue.CleanupOldJobs(retentionDays); err != nil {
Logger.Errorf("Failed to cleanup old jobs: %v", err)
} else {
Logger.Infof("Job cleanup completed successfully")
}
})

if err != nil {
return err
}

mw.cron.Start()
Logger.Infof("Maintenance worker started with schedule: %s", schedule)
return nil
}

func (mw *MaintenanceWorker) Stop() {
mw.cron.Stop()
}
Loading
Loading