From e46e91a1d4180aa403c075273ae7ddf57f78e85b Mon Sep 17 00:00:00 2001 From: Artyom Garibyan Date: Sat, 22 Nov 2025 18:42:17 +0400 Subject: [PATCH] Upload working version --- README.md | 54 ++++++++++- go.mod | 25 +++++ go.sum | 69 ++++++++++++++ main.go | 19 ++++ pkg/api/addtask.go | 54 +++++++++++ pkg/api/api.go | 211 +++++++++++++++++++++++++++++++++++++++++++ pkg/api/task.go | 70 ++++++++++++++ pkg/db/db.go | 39 ++++++++ pkg/db/nextdate.go | 72 +++++++++++++++ pkg/db/scheduler.db | Bin 0 -> 12288 bytes pkg/db/task.go | 160 ++++++++++++++++++++++++++++++++ pkg/server/server.go | 22 +++++ tasks.db | 0 tests/db_2_test.go | 3 +- tests/settings.go | 13 ++- 15 files changed, 802 insertions(+), 9 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/api/addtask.go create mode 100644 pkg/api/api.go create mode 100644 pkg/api/task.go create mode 100644 pkg/db/db.go create mode 100644 pkg/db/nextdate.go create mode 100644 pkg/db/scheduler.db create mode 100644 pkg/db/task.go create mode 100644 pkg/server/server.go create mode 100644 tasks.db diff --git a/README.md b/README.md index 597678a..df90e52 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,53 @@ -# Файлы для итогового задания +# Планировщик задач -В директории `tests` находятся тесты для проверки API, которое должно быть реализовано в веб-сервере. +## Описание проекта -Директория `web` содержит файлы фронтенда. \ No newline at end of file +Планировщик задач — веб-приложение для управления задачами с поддержкой дедлайнов и повторяющихся событий. + +Каждая задача содержит дату дедлайна, заголовок и комментарий. Задачи могут повторяться по заданному правилу: ежегодно, через определённое количество дней, в конкретные дни месяца или недели. При отметке повторяющейся задачи как выполненной она автоматически переносится на следующую дату согласно правилу. Обычные задачи при выполнении удаляются. + +### API поддерживает следующие операции: + +- Добавление задачи +- Получение списка задач +- Удаление задачи +- Получение параметров задачи +- Изменение параметров задачи +- Отметка задачи как выполненной + +## Структура проекта + +- `tests/` — тесты для проверки API +- `web/` — файлы фронтенда +- `pkg/server/` — запуск сервера на `http://localhost:7540/` +- `pkg/api/` — обработчики HTTP-запросов +- `pkg/db/` — функции для CRUD-операций с базой данных + +## Задания повышенной трудности + +Задания повышенной трудности не выполнялись. + +## Запуск проекта локально + +```bash +go run . +``` + +После запуска сервер будет доступен по адресу: **http://localhost:7540/** + +## Запуск тестов + +```bash +go test ./tests +``` + +### Параметры в `tests/settings.go`: + +```go +FullNextDate = false +Search = false +``` + +## Docker + +Сборка и запуск через Docker не реализованы. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cc0a700 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module github.com/ArtyomGaribyan/Task-Scheduler + +go 1.24.0 + +require ( + github.com/jmoiron/sqlx v1.4.0 + github.com/stretchr/testify v1.11.1 + modernc.org/sqlite v1.40.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/sys v0.36.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.66.10 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c493f2c --- /dev/null +++ b/go.sum @@ -0,0 +1,69 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= +modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= +modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.40.0 h1:bNWEDlYhNPAUdUdBzjAvn8icAs/2gaKlj4vM+tQ6KdQ= +modernc.org/sqlite v1.40.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2c14f33 --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + + "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" + "github.com/ArtyomGaribyan/Task-Scheduler/pkg/server" +) + +func main() { + go func() { + err := db.InitDB() + if err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + }() + + server.Run() +} diff --git a/pkg/api/addtask.go b/pkg/api/addtask.go new file mode 100644 index 0000000..580a370 --- /dev/null +++ b/pkg/api/addtask.go @@ -0,0 +1,54 @@ +package api + +import ( + "fmt" + "time" + + "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" +) + +func checkTask (task *db.Task) error { +if task == nil { + return fmt.Errorf("task is nil") + } + if task.Title == "" { + return fmt.Errorf("title is required") + } + now := time.Now() + today := now.Format(db.DateLayout) + if task.Date == "" { + task.Date = today + } else { + _, err := time.Parse(db.DateLayout, task.Date) + if err != nil { + return fmt.Errorf("invalid date format: %v", err) + } + } + + var err error + if task.Date < today { + if task.Repeat != "" { + task.Date, err = db.NextDate(now, task.Date, task.Repeat) + if err != nil { + return err + } + } else { + task.Date = today + } + } + return nil +} + +func addTaskHandler(task *db.Task) (int64, error) { + err := checkTask(task) + if err != nil { + return 0, err + } + + id, err := db.AddTask(task) + if err != nil { + return 0, err + } + + return id, nil +} diff --git a/pkg/api/api.go b/pkg/api/api.go new file mode 100644 index 0000000..7ff5fa6 --- /dev/null +++ b/pkg/api/api.go @@ -0,0 +1,211 @@ +package api + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" + "time" + + "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" +) + +func writeJson(w http.ResponseWriter, data any) { + fmt.Printf("\n") + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + jsonData, err := json.Marshal(data) + if err != nil { + http.Error(w, "Error encoding JSON", http.StatusInternalServerError) + return + } + w.Write(jsonData) +} + +func HandleNextDate(w http.ResponseWriter, r *http.Request) { + now := r.URL.Query().Get("now") + date := r.URL.Query().Get("date") + repeat := r.URL.Query().Get("repeat") + + var parsedNow time.Time + var err error + if now == "" { + parsedNow = time.Now() + } else { + parsedNow, err = time.Parse(db.DateLayout, now) + if err != nil { + w.Write([]byte("")) + return + } + } + + nextDate, err := db.NextDate(parsedNow, date, repeat) + if err != nil { + w.Write([]byte("")) + return + } + + w.Write([]byte(nextDate)) +} + +func HandleTask(w http.ResponseWriter, r *http.Request) { + var task db.Task + + method := r.Method + log.Println("Method:", method) + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&task) + if err != nil { + task.ID = r.URL.Query().Get("id") + task.Title = r.URL.Query().Get("title") + task.Date = r.URL.Query().Get("date") + task.Comment = r.URL.Query().Get("comment") + task.Repeat = r.URL.Query().Get("repeat") + if task != (db.Task{}) { + goto If_close + } + + task.Error = "Invalid request body: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } +If_close: + + log.Println("Received task:", task) + + switch method { + case http.MethodGet: + if task.ID == "" { + task.Error = "Error getting task: missing ID" + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + + taskResieved, err := db.GetTask(task.ID) + log.Println("Task received:", taskResieved.ID, taskResieved.Title, taskResieved.Date, taskResieved.Comment, taskResieved.Repeat) + if err != nil { + task.Error = "Error getting task: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + writeJson(w, taskResieved) + return + + case http.MethodPost: + log.Println("Adding task:", task) + id, err := addTaskHandler(&task) + if err != nil { + task.Error = "Error adding task: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + task.ID = strconv.Itoa(int(id)) + log.Println("Added task ID:", task.ID) + writeJson(w, task) + return + + case http.MethodPut: + if task.ID == "" { + task.Error = "Error updating task: missing ID" + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + + log.Println("Updating task:", task.ID, "\n", task.Title, task.Date, task.Comment, task.Repeat) + err := UpdateTaskHandler(&task) + if err != nil { + task.Error = "Error updating task: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + writeJson(w, db.Task{}) + case http.MethodDelete: + if task.ID == "" { + task.Error = "Error deleting task: missing ID" + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + + err := db.DeleteTask(task.ID) + if err != nil { + task.Error = "Error deleting task: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + + log.Printf("Successfully deleted task: %s\n", task.ID) + writeJson(w, db.Task{}) + + default: + task.Error = "Method not allowed" + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } +} + +func HandleTaskDone(w http.ResponseWriter, r *http.Request) { + var task db.Task + + method := r.Method + log.Println("Method:", method) + if method != http.MethodPost { + log.Println("Method not allowed") + w.WriteHeader(http.StatusInternalServerError) + task.Error = "Method not allowed" + writeJson(w, task) + return + } + + task.ID = r.URL.Query().Get("id") + if task.ID == "" { + task.Error = "Error marking task as done: missing ID" + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + log.Println("Marking task as done", task.ID) + + err := TaskDoneHandler(task.ID) + if err != nil { + task.Error = "Error marking task as done: " + err.Error() + log.Println(task.Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, task) + return + } + + log.Println("Successfully marked as done: ", task.ID) + writeJson(w, db.Task{}) +} + +func Init() { + http.Handle("/", http.FileServer(http.Dir("web"))) + http.HandleFunc("/api/nextdate", HandleNextDate) + http.HandleFunc("api/nextdate", HandleNextDate) + http.HandleFunc("/api/task", HandleTask) + http.HandleFunc("api/task", HandleTask) + http.HandleFunc("/api/tasks", HandleTasks) + http.HandleFunc("api/tasks", HandleTasks) + http.HandleFunc("/api/task/done", HandleTaskDone) + http.HandleFunc("api/task/done", HandleTaskDone) +} diff --git a/pkg/api/task.go b/pkg/api/task.go new file mode 100644 index 0000000..76264bd --- /dev/null +++ b/pkg/api/task.go @@ -0,0 +1,70 @@ +package api + +import ( + "log" + "net/http" + "time" + + "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" +) + +type TasksResp struct { + Tasks []*db.Task `json:"tasks"` +} + +func HandleTasks(w http.ResponseWriter, r *http.Request) { + tasks, err := db.Tasks(50) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, TasksResp{ + Tasks: []*db.Task{}, + }) + return + } + log.Println("Tasks fetched in handler:", tasks) + writeJson(w, TasksResp{ + Tasks: tasks, + }) +} + +func UpdateTaskHandler(task *db.Task) error { + err := checkTask(task) + if err != nil { + return err + } + + err = db.UpdateTask(task) + if err != nil { + return err + } + + return nil +} +func TaskDoneHandler(id string) error { + task, err := db.GetTask(id) + if err != nil { + return err + } + + if task.Repeat == "" { + err = db.DeleteTask(id) + if err != nil { + return err + } + return nil + } + + log.Println("Calculating next date for task:", task) + task.Date, err = db.NextDate(time.Now(), task.Date, task.Repeat) + if err != nil { + return err + } + log.Println("Next date for task:", task.Date) + + err = db.UpdateDate(&task) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/db/db.go b/pkg/db/db.go new file mode 100644 index 0000000..b63c270 --- /dev/null +++ b/pkg/db/db.go @@ -0,0 +1,39 @@ +package db + +import ( + "database/sql" + "log" + + "github.com/ArtyomGaribyan/Task-Scheduler/tests" + + _ "modernc.org/sqlite" +) + +func InitDB() error { + log.Println("Opening database file:", tests.DBFile) + + db, err := sql.Open(tests.SQL, tests.DBFile) + if err != nil { + return err + } + defer db.Close() + + err = db.Ping() + if err != nil { + return err + } + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS scheduler ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date CHAR(8) NOT NULL DEFAULT '', + title VARCHAR(50) NOT NULL DEFAULT '', + comment TEXT NOT NULL DEFAULT '', + repeat VARCHAR(20) NOT NULL DEFAULT '' + )`) + if err != nil { + return err + } + log.Println("Database initialized successfully") + + return nil +} diff --git a/pkg/db/nextdate.go b/pkg/db/nextdate.go new file mode 100644 index 0000000..b5a26e2 --- /dev/null +++ b/pkg/db/nextdate.go @@ -0,0 +1,72 @@ +package db + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" +) + +const DateLayout = "20060102" + +func caseD(now, date time.Time, repeatSplitted []string) (time.Time, error) { + if len(repeatSplitted) != 2 { + return time.Time{}, fmt.Errorf("invalid date format") + } + + days, err := strconv.Atoi(repeatSplitted[1]) + if err != nil { + return time.Time{}, fmt.Errorf("invalid date format: %v", err) + } + + if days <= 0 || days > 400 { + return time.Time{}, fmt.Errorf("invalid date format: days value too large") + } + + log.Println("Calculating next date by days:", days, "from", date, "with now =", now) + date = date.AddDate(0, 0, days) + for date.Before(now) || date.Equal(now) { + date = date.AddDate(0, 0, days) + } + return date, nil +} + +func caseY(now, date time.Time) time.Time { + date = date.AddDate(1, 0, 0) + for date.Before(now) || date.Equal(now) { + date = date.AddDate(1, 0, 0) + } + return date +} + +func NextDate(now time.Time, dstart, repeat string) (string, error) { + var nextDate time.Time + dateStart, err := time.Parse(DateLayout, dstart) + if err != nil { + return "", fmt.Errorf("invalid date format: %v", err) + } + + repeatSplitted := strings.Fields(repeat) + if len(repeatSplitted) == 0 || repeatSplitted[0] == "" { + return "", fmt.Errorf("empty repeat pattern") + } + if len(repeatSplitted) > 2 { + return "", fmt.Errorf("invalid date format") + } + log.Println("Repeat pattern: " + strings.Join(repeatSplitted, "~") + "!") + + switch repeatSplitted[0] { + case "d": + nextDate, err = caseD(now, dateStart, repeatSplitted) + if err != nil { + return "", err + } + case "y": + nextDate = caseY(now, dateStart) + default: + return "", fmt.Errorf("invalid date format") + } + + return nextDate.Format(DateLayout), nil +} diff --git a/pkg/db/scheduler.db b/pkg/db/scheduler.db new file mode 100644 index 0000000000000000000000000000000000000000..03ce1e6104e4443870738a3049264ee7c8dc22d3 GIT binary patch literal 12288 zcmeHN-%le|7@dM!XqRdI!De3|T%#)>EX+(Tt*epkva>9ql%-o%iHWAtaV7F&fv$;f z+^Osumt=KcG$yW)^=INkL2$dJRG)nC>5h*YW8z=ntIHW^R z-#x>8-Kkt9b}lB8#MWI^i^YWbmwVxPgD=ZbP@}l3F~A>Y|CVP!e|Gw)vR*V9B*oJq%IaYN8d0K zSI3Q++0sGPcAnDLq{U6JxyBNtFA~#x^(eUz?HdS1M@YXuLPEo_!M+Hf19~LZUJOmd z@r>PbJ``;UwvxzTj6{aRVRBYK7a9)7NON<$+vQ4*CC3eNDHJWl`ITJ*N2ewxjL9U4 z=`Vrk{$w-86$8)nB{uE;Y;IR8e>Sg^FH7DH?}9h#b$hs!=%wcY4GdPGOPKmDCeO1C!UMYy z$ZoS`i&`o69rET2$*?uTz6VztCrC9x{0;ORm|A7Iyz;NGYNU^dHH$V<9m6NK2=iHG zFhSI3=vjNouLE0wiA4_vOM961CY1o z*@l%WKxhbz>Xv14t-pl`>s$!z*=~9oA$BfkH+^9XgR1%W{V&l?pSR6$$~Hs5HiO?b zgU>dDYMY_cHp5BV3~e?Ug0vM32P!fuZ2`2Wv2qvgtWT&V8S5HeDPD2{C^;%^-iA@x zgE77IDPStaOyyPMK*g+=o;Wn7Cl8J3i9=&j%wyo%Uz;-P0kKong65-q(gU?A^O0?9 z*A9GT8%$mhc5hRrhT%Qhl*y~X{@awP0JeLZGI@nK)HY>yfN{?@6*PHK87kkEepnTX z76-e6rpJ8;g;}vEVYkpNDf<4sl5u$1YzH(nTe_jqpDH(60lH9V+IQH;Y_VWN@-Pta zFua47Y0)&TIqN3cI;as33ba~wPd9n3Qin>Oq{v=$8!JT)_5n|6GpP-pWHIrA*Wl(U z2X7NFp9as1WpChvjVZ8fE)O|oc__=F?7*6#gJ-Q2{FKsR@>(}gZt_011P8#=E7~z` zNS3!|aN6V*t~XE;L@PFIB$(qC!kQ!2J1{TnuopRw 0 { dbfile = envFile @@ -48,6 +48,7 @@ func TestDB(t *testing.T) { assert.NoError(t, err) id, err := res.LastInsertId() + assert.NoError(t, err) var task Task err = db.Get(&task, `SELECT * FROM scheduler WHERE id=?`, id) diff --git a/tests/settings.go b/tests/settings.go index 3908fdf..d058235 100644 --- a/tests/settings.go +++ b/tests/settings.go @@ -1,7 +1,10 @@ package tests -var Port = 7540 -var DBFile = "../scheduler.db" -var FullNextDate = false -var Search = false -var Token = `` +var ( + Port = 7540 + DBFile = "../Task-Scheduler/pkg/db/scheduler.db" + FullNextDate = false + Search = false + Token = `` + SQL = "sqlite" +)