Skip to content

Commit 7c73a3e

Browse files
committed
feat(api): add API key authentication for task management and list tasks endpoint
1 parent 8e37549 commit 7c73a3e

File tree

5 files changed

+121
-5
lines changed

5 files changed

+121
-5
lines changed

README.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### 🚀 Try Schedify: [schedify.dev](https://schedify.dev)
2+
23
No auth required. No infrastructure to manage. Just simple, reliable task scheduling for developers.
4+
35
- 📖 [Read our Dead Simple Integration Guide](https://schedify.dev/5-min-guide)
46
- 🆓 [Start Scheduling - It's free](https://schedify.dev/schedules)
57

@@ -11,6 +13,36 @@ No auth required. No infrastructure to manage. Just simple, reliable task schedu
1113
1214
Schedy lets you schedule HTTP POST requests to any endpoint at any time, with custom headers and payloads. Perfect for webhooks, bots, reminders, integrations, and all sorts of automation—without the bloat.
1315

16+
## 🐳 Try Schedy in 1 Minute
17+
18+
You can run Schedy instantly using Docker from either GitHub Container Registry or Docker Hub:
19+
20+
**From GitHub Container Registry:**
21+
22+
```sh
23+
docker run -p 8080:8080 ghcr.io/ksamirdev/schedy:latest
24+
```
25+
26+
**From Docker Hub:**
27+
28+
```sh
29+
docker run -p 8080:8080 ksamirdev/schedy:latest
30+
```
31+
32+
You can also use a specific version tag (e.g., `v0.0.1`):
33+
34+
```sh
35+
docker run -p 8080:8080 ghcr.io/ksamirdev/schedy:v0.0.1
36+
# or
37+
docker run -p 8080:8080 ksamirdev/schedy:v0.0.1
38+
```
39+
40+
Set an API key for security (optional but recommended):
41+
42+
```sh
43+
docker run -p 8080:8080 -e SCHEDY_API_KEY=your-secret ghcr.io/ksamirdev/schedy:latest
44+
```
45+
1446
---
1547

1648
## Features
@@ -33,22 +65,24 @@ Head to [Releases](https://github.com/ksamirdev/schedy/releases) and grab the la
3365
### 2. Run
3466

3567
```bash
36-
./schedy --port 8081
68+
SCHEDY_API_KEY=your-secret ./schedy --port 8081
3769
```
3870

3971
Schedy will listen on the port you specify with `--port` (default: `8080`).
72+
If you set the `SCHEDY_API_KEY` environment variable, all API endpoints require the `X-API-Key` header.
4073

4174
---
4275

4376
## API
4477

4578
### Schedule a Task
4679

47-
Send a POST to `/tasks`:
80+
Send a POST to `/tasks` (requires `X-API-Key` header if enabled):
4881

4982
```bash
5083
curl -X POST http://localhost:8080/tasks \
5184
-H "Content-Type: application/json" \
85+
-H "X-API-Key: your-secret" \
5286
-d '{
5387
"execute_at": "2025-05-26T15:00:00Z",
5488
"url": "https://webhook.site/your-endpoint",
@@ -58,6 +92,7 @@ curl -X POST http://localhost:8080/tasks \
5892
```
5993

6094
#### Request Fields
95+
6196
- `execute_at`: When to run (RFC3339, UTC)
6297
- `url`: Where to POST
6398
- `headers`: (optional) Map of HTTP headers
@@ -66,9 +101,11 @@ curl -X POST http://localhost:8080/tasks \
66101
#### Examples
67102

68103
**JSON payload (default):**
104+
69105
```bash
70106
curl -X POST http://localhost:8080/tasks \
71107
-H "Content-Type: application/json" \
108+
-H "X-API-Key: your-secret" \
72109
-d '{
73110
"execute_at": "2025-05-26T15:00:00Z",
74111
"url": "https://example.com/webhook",
@@ -77,9 +114,11 @@ curl -X POST http://localhost:8080/tasks \
77114
```
78115

79116
**Form data:**
117+
80118
```bash
81119
curl -X POST http://localhost:8080/tasks \
82120
-H "Content-Type: application/json" \
121+
-H "X-API-Key: your-secret" \
83122
-d '{
84123
"execute_at": "2025-05-26T15:00:00Z",
85124
"url": "https://example.com/form",
@@ -89,9 +128,11 @@ curl -X POST http://localhost:8080/tasks \
89128
```
90129

91130
**Plain text:**
131+
92132
```bash
93133
curl -X POST http://localhost:8080/tasks \
94134
-H "Content-Type: application/json" \
135+
-H "X-API-Key: your-secret" \
95136
-d '{
96137
"execute_at": "2025-05-26T15:00:00Z",
97138
"url": "https://example.com/text",
@@ -100,9 +141,21 @@ curl -X POST http://localhost:8080/tasks \
100141
}'
101142
```
102143

144+
### List Scheduled Tasks
145+
146+
Send a GET to `/tasks/list` (requires `X-API-Key` header if enabled):
147+
148+
```bash
149+
curl -X GET http://localhost:8080/tasks/list \
150+
-H "X-API-Key: your-secret"
151+
```
152+
153+
Returns a JSON array of all scheduled tasks.
154+
103155
---
104156

105157
## Why Schedy?
158+
106159
- No cron, no YAML, no UI, no cloud lock-in
107160
- Just HTTP, just works
108161
- For hackers, tinkerers, and anyone who wants to automate the weird stuff

cmd/schedy/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ func main() {
2828
handler := api.New(store)
2929

3030
mux := http.NewServeMux()
31-
mux.HandleFunc("/tasks", handler.CreateTask)
31+
mux.HandleFunc("POST /tasks", handler.WithAuth(handler.CreateTask))
32+
mux.HandleFunc("GET /tasks", handler.WithAuth(handler.ListTasks))
3233

3334
addr := ":" + *port
3435
srv := &http.Server{Addr: addr, Handler: mux}

internal/api/handler.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,41 @@ package api
33
import (
44
"encoding/json"
55
"net/http"
6+
"os"
67
"time"
78

89
"github.com/google/uuid"
910
"github.com/ksamirdev/schedy/internal/scheduler"
1011
)
1112

1213
type Handler struct {
13-
Store scheduler.Store
14+
Store scheduler.Store
15+
APIKey string
1416
}
1517

1618
func New(store scheduler.Store) *Handler {
17-
return &Handler{Store: store}
19+
return &Handler{
20+
Store: store,
21+
APIKey: os.Getenv("SCHEDY_API_KEY"),
22+
}
23+
}
24+
25+
// Middleware to check API key
26+
func (h *Handler) WithAuth(next http.HandlerFunc) http.HandlerFunc {
27+
return func(w http.ResponseWriter, r *http.Request) {
28+
if h.APIKey != "" {
29+
key := r.Header.Get("X-API-Key")
30+
if key == "" {
31+
http.Error(w, "missing API key", http.StatusUnauthorized)
32+
return
33+
}
34+
if key != h.APIKey {
35+
http.Error(w, "invalid API key", http.StatusForbidden)
36+
return
37+
}
38+
}
39+
next(w, r)
40+
}
1841
}
1942

2043
func (h *Handler) CreateTask(w http.ResponseWriter, r *http.Request) {
@@ -55,3 +78,14 @@ func (h *Handler) CreateTask(w http.ResponseWriter, r *http.Request) {
5578
w.WriteHeader(http.StatusCreated)
5679
json.NewEncoder(w).Encode(task)
5780
}
81+
82+
// ListTasks returns all scheduled tasks (no status yet)
83+
func (h *Handler) ListTasks(w http.ResponseWriter, r *http.Request) {
84+
tasks, err := h.Store.ListTasks()
85+
if err != nil {
86+
http.Error(w, "could not list tasks", http.StatusInternalServerError)
87+
return
88+
}
89+
w.Header().Set("Content-Type", "application/json")
90+
json.NewEncoder(w).Encode(tasks)
91+
}

internal/scheduler/badger_store.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,30 @@ func (s *BadgerStore) GetDueTasks(start, end time.Time) ([]Task, error) {
7575
})
7676
return tasks, err
7777
}
78+
79+
func (s *BadgerStore) ListTasks() ([]Task, error) {
80+
var tasks []Task
81+
82+
err := s.db.View(func(txn *badger.Txn) error {
83+
it := txn.NewIterator(badger.DefaultIteratorOptions)
84+
defer it.Close()
85+
86+
prefix := []byte("task:")
87+
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
88+
item := it.Item()
89+
err := item.Value(func(val []byte) error {
90+
var t Task
91+
if err := json.Unmarshal(val, &t); err == nil {
92+
tasks = append(tasks, t)
93+
}
94+
return nil
95+
})
96+
if err != nil {
97+
return err
98+
}
99+
}
100+
return nil
101+
})
102+
103+
return tasks, err
104+
}

internal/scheduler/store.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ type Store interface {
66
Save(task Task) error
77
Delete(id string, timestamp int64) error
88
GetDueTasks(start, end time.Time) ([]Task, error)
9+
ListTasks() ([]Task, error)
910
}

0 commit comments

Comments
 (0)