diff --git a/.gitignore b/.gitignore index f053e23..06a0372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.json -*.db +data/ +.DS_Store +.env +.vscode \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index 6108a7d..31f328a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,15 @@ APP="My daily control questions" -.PHONY: run_linter +.PHONY: run_linter run_app run_linter: golangci-lint run + +run_app: + set -a; . .env; set +a; go run cmd/main.go ask + +# Запуск приложения с нуля для проверки работы +start_from_scratch: + rm -rf data + mkdir data/ + set -a; . .env; set +a; go run cmd/main.go ask diff --git a/README.md b/README.md index 39e9716..fd23a88 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,4 @@ - разбит по неделям - каждая неделя - один лист - ~~Создать makefile и прикрутить линтер~~ ([MR](https://github.com/tulashvili/MyDailyControlQuestions/commit/b381af895f580a186e9eb7a07be5a0b26dc70f4f)) +- Добавить возможность настройки бэкапа данных по расписанию diff --git a/cmd/main.go b/cmd/main.go index 1f596a4..78633a8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,38 +1,26 @@ package main import ( - "fmt" - "log" + "log/slog" + "os" - "github.com/tulashvili/MyDailyControlQuestions/internal/storage" - "github.com/tulashvili/MyDailyControlQuestions/internal/ui" -) - -const ( - sqliteStoragePath = "data/sqlite3.db" + app "github.com/tulashvili/MyDailyControlQuestions/internal" + "github.com/tulashvili/MyDailyControlQuestions/internal/config" + "github.com/tulashvili/MyDailyControlQuestions/internal/ui/cli" ) func main() { - // questions := service.GetQuestions() - // answers := ui.AskQuestion(questions) - - conn, err := storage.InitDB(sqliteStoragePath) + conf, err := config.NewConfig(true) if err != nil { - log.Fatal(err) + slog.Error("failed to load config", slog.Any("error", err)) + os.Exit(1) } - if err := storage.CreateTable(conn); err != nil { - log.Fatal(err) + app, err := app.NewApp(*conf) + if err != nil { + slog.Error("failed to create app", slog.Any("error", err)) + os.Exit(1) } + cli.Execute(app.DB) - // for _, answer := range answers { - // if err := storage.InsertRow(conn, answer); err != nil { - // log.Fatal(err) - // } - // } - fmt.Println("✅ Данные успешно добавлены в таблицу daily_log") // change to log? - - if err := ui.ShowDataOverPeriod(conn); err != nil { - log.Panic(err) - } } diff --git a/go.mod b/go.mod index 56fcb6b..35f8f9c 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,11 @@ module github.com/tulashvili/MyDailyControlQuestions go 1.25.4 require github.com/mattn/go-sqlite3 v1.14.33 + +require ( + github.com/caarlos0/env/v11 v11.3.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/go.sum b/go.sum index 3de9741..c4bc0cd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,16 @@ +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/app.go b/internal/app.go new file mode 100644 index 0000000..bfe6ab8 --- /dev/null +++ b/internal/app.go @@ -0,0 +1,35 @@ +package app + +import ( + "database/sql" + "log" + + "github.com/tulashvili/MyDailyControlQuestions/internal/config" + + "github.com/tulashvili/MyDailyControlQuestions/pkg/db/sqlitedb" +) + +type App struct { + DB *sql.DB + Config config.Config +} + +func NewApp(conf config.Config) (App, error) { + app := &App{ + Config: conf, + } + + // DB + var err error + app.DB, err = sqlitedb.InitDB(conf.DbPath) + if err != nil { + log.Fatal(err) + } + + if err := sqlitedb.CreateTable(app.DB); err != nil { + log.Fatal(err) + } + return App{ + DB: app.DB, + }, err +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..95be10d --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,36 @@ +package config + +import ( + "fmt" + "log/slog" + "os" + + "github.com/joho/godotenv" + "github.com/tulashvili/MyDailyControlQuestions/pkg/db" +) + +type Config struct { + DbPath db.DataSource +} + +func NewConfig(loadDotEnv bool) (*Config, error) { + if err := os.Setenv("TZ", "UTC"); err != nil { + return nil, err + } + + if loadDotEnv { + if err := godotenv.Load(".env"); err == nil { + slog.Info("loaded .env file") + } + } + + c := &Config{ + DbPath: db.LoadDatasource(), + } + + if c.DbPath.SqlitePath == "" { + return nil, fmt.Errorf("DB_PATH is empty") + } + + return c, nil +} diff --git a/internal/domain/answer.go b/internal/domain/answer.go new file mode 100644 index 0000000..1f32cad --- /dev/null +++ b/internal/domain/answer.go @@ -0,0 +1,23 @@ +package domain + +import "time" + +type UserAnswer struct { + ID int + QuestionCategory string + QuestionText string + Answer int + AnsweredAt *time.Time +} + +// Init user answer +func CreateUserAnswer(question Question, answer int) UserAnswer { + now := time.Now() + + return UserAnswer{ + QuestionCategory: string(question.Category), + QuestionText: question.Text, + Answer: answer, + AnsweredAt: &now, + } +} \ No newline at end of file diff --git a/internal/service/service.go b/internal/domain/question.go similarity index 53% rename from internal/service/service.go rename to internal/domain/question.go index a8b69e6..93c5177 100644 --- a/internal/service/service.go +++ b/internal/domain/question.go @@ -1,18 +1,18 @@ -package service +package domain -import ( - "fmt" - "time" +type Question struct { + Category + Text string +} - "github.com/tulashvili/MyDailyControlQuestions/internal/models" -) +type Category string const ( - Biology models.Category = "Биология" - Psychology models.Category = "Психология" + Biology Category = "Биология" + Psychology Category = "Психология" ) -var QUESTIONS = []models.Question{ +var QUESTIONS = []Question{ { Category: Psychology, Text: "Насколько высокий уровень стресса сегодня? (1 - очень стрессовый день, 5 - стресса почти не было)", @@ -33,34 +33,4 @@ var QUESTIONS = []models.Question{ Category: Biology, Text: "В рационе было достаточно белка и клетчатки? (1 - не было, 5 - да)", }, -} - -// Return question list with category -func GetQuestions() []models.Question { - return QUESTIONS -} - -// Validate user answer -func ValidateAnswer(answer int) error { - if answer < 1 || answer > 5 { - return fmt.Errorf("answer must be between 1 and 5") - } - return nil -} - -// Init user answer -func CreateUserAnswer(question models.Question, answer int) models.UserAnswer { - now := time.Now() - - - return models.UserAnswer{ - QuestionCategory: string(question.Category), - QuestionText: question.Text, - Answer: answer, - AnsweredAt: &now, - } -} - - - - +} \ No newline at end of file diff --git a/internal/domain/rules.go b/internal/domain/rules.go new file mode 100644 index 0000000..e256776 --- /dev/null +++ b/internal/domain/rules.go @@ -0,0 +1,11 @@ +package domain + +import "errors" + +// Validate user answer +func ValidateAnswer(answer int) error { + if answer < 1 || answer > 5 { + return errors.New("answer must be between 1 and 5") + } + return nil +} diff --git a/internal/models/models.go b/internal/models/models.go deleted file mode 100644 index 50ad4ad..0000000 --- a/internal/models/models.go +++ /dev/null @@ -1,20 +0,0 @@ -package models - -import ( - "time" -) - -type UserAnswer struct { - ID int - QuestionCategory string - QuestionText string - Answer int - AnsweredAt *time.Time -} - -type Question struct { - Category Category - Text string -} - -type Category string diff --git a/internal/repo/answer_repository.go b/internal/repo/answer_repository.go new file mode 100644 index 0000000..fab2648 --- /dev/null +++ b/internal/repo/answer_repository.go @@ -0,0 +1,8 @@ +package repo + +import "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + +type AnswerRepository interface { + SaveAnswer(answer domain.UserAnswer) error + GetAnswers(period int) ([]domain.UserAnswer, error) +} diff --git a/internal/repo/sqlite_repository.go b/internal/repo/sqlite_repository.go new file mode 100644 index 0000000..a3e98df --- /dev/null +++ b/internal/repo/sqlite_repository.go @@ -0,0 +1,77 @@ +package repo + +import ( + "database/sql" + "fmt" + "time" + + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" +) + +type SQLiteAnswerRepository struct { + db *sql.DB +} + +func NewSQLiteAnswerRepository(db *sql.DB) *SQLiteAnswerRepository { + return &SQLiteAnswerRepository{db: db} +} + +// Save answers to questions +func (r *SQLiteAnswerRepository) SaveAnswer(answer domain.UserAnswer) error { + query := ` + INSERT INTO daily_log (category, question, answer, answeredAt) + VALUES (?, ?, ?, ?) + ` + _, err := r.db.Exec( + query, + answer.QuestionCategory, + answer.QuestionText, + answer.Answer, + answer.AnsweredAt.UTC().Format(time.RFC3339Nano), + ) + return err +} + +// Get data over some period +func (r *SQLiteAnswerRepository) GetAnswers(period int) ([]domain.UserAnswer, error) { + periodDay := fmt.Sprintf("-%d days", period) + query := ` + SELECT category, question, answer, answeredAt + FROM daily_log + WHERE answeredAt >= datetime('now', ?); + ` + + rows, err := r.db.Query(query, periodDay) + if err != nil { + return nil, err + } + defer rows.Close() + + var result []domain.UserAnswer + var answeredAtStr sql.NullString + + for rows.Next() { + var ua domain.UserAnswer + + if err := rows.Scan( + &ua.QuestionCategory, + &ua.QuestionText, + &ua.Answer, + &answeredAtStr, + ); err != nil { + return nil, err + } + + if answeredAtStr.Valid { + t, err := time.Parse(time.RFC3339Nano, answeredAtStr.String) + if err != nil { + return nil, err + } + ua.AnsweredAt = &t + } + + result = append(result, ua) + } + + return result, nil +} diff --git a/internal/storage/db.go b/internal/storage/db.go deleted file mode 100644 index def3666..0000000 --- a/internal/storage/db.go +++ /dev/null @@ -1,31 +0,0 @@ -package storage - -import ( - "database/sql" - "fmt" - - _ "github.com/mattn/go-sqlite3" -) - -func InitDB(databasePath string) (*sql.DB, error) { - fmt.Printf("🔌 Соединение с базой %s установлено\n", databasePath) // change to log? - return sql.Open("sqlite3", databasePath) - -} - -func CreateTable(conn *sql.DB) error { - query := ` - CREATE TABLE IF NOT EXISTS daily_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - category TEXT NOT NULL, - question TEXT NOT NULL, - answer INTEGER NOT NULL, - answeredAt TEXT NOT NULL, - - UNIQUE (answeredAt) - ); - ` - _, err := conn.Exec(query) - fmt.Println("✅ Таблица daily_log готова к использованию") // change to log? - return err -} diff --git a/internal/storage/repo.go b/internal/storage/repo.go deleted file mode 100644 index bc65dea..0000000 --- a/internal/storage/repo.go +++ /dev/null @@ -1,65 +0,0 @@ -package storage - -import ( - "database/sql" - "fmt" - - "github.com/tulashvili/MyDailyControlQuestions/internal/models" -) - -type UserAnswerRow struct { - ID int - Category string - Question string - Answer int - AnsweredAt string -} - -// ToDo: Create interface for db methods ?? -// type .... interface { .... } - -// Insert question answers -func InsertRow(conn *sql.DB, data models.UserAnswer) error { - query := ` - INSERT INTO daily_log (category, question, answer, answeredAt) - VALUES (?, ?, ?, ?) - ` - _, err := conn.Exec( - query, - data.QuestionCategory, - data.QuestionText, - data.Answer, - data.AnsweredAt, - ) - return err -} - -// Get data over some period -func SelectRows(conn *sql.DB, period int) ([]UserAnswerRow, error) { - periodDay := fmt.Sprintf("-%d days", period) - query := ` - SELECT * FROM daily_log - WHERE answeredAt >= datetime('now', ?); - ` - - response, err := conn.Query(query, periodDay) - - userAnswerRow := make([]UserAnswerRow, 0) - - for response.Next() { - var row UserAnswerRow - - err := response.Scan( - &row.ID, - &row.Category, - &row.Question, - &row.Answer, - &row.AnsweredAt, - ) - if err != nil { - return nil, err - } - userAnswerRow = append(userAnswerRow, row) - } - return userAnswerRow, err -} diff --git a/internal/ui/cli/ask_questions.go b/internal/ui/cli/ask_questions.go new file mode 100644 index 0000000..2f0e311 --- /dev/null +++ b/internal/ui/cli/ask_questions.go @@ -0,0 +1,27 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cli + +import ( + "github.com/spf13/cobra" + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + "github.com/tulashvili/MyDailyControlQuestions/internal/repo" + "github.com/tulashvili/MyDailyControlQuestions/internal/usecase" +) + +// askQuestionsCmd represents the askQuestions command +var askQuestionsCmd = &cobra.Command{ + Use: "ask", + Short: "Запустить опрос", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + repo := repo.NewSQLiteAnswerRepository(db) + ask := usecase.NewAskQuestion(repo) + io := QuestionAdapter{} + + if err := ask.Execute(domain.QUESTIONS, io); err != nil { + panic(err) + } + }, +} diff --git a/internal/ui/cli/list_questions.go b/internal/ui/cli/list_questions.go new file mode 100644 index 0000000..db79fb0 --- /dev/null +++ b/internal/ui/cli/list_questions.go @@ -0,0 +1,26 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tulashvili/MyDailyControlQuestions/internal/usecase" +) + +var listQuestionsCmd = &cobra.Command{ + Use: "list_questions", + Short: "Получить список текущих вопросов", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + questions := usecase.GetQuestionList() + + for _, question := range questions { + fmt.Println("Категория:", question.Category) + fmt.Println("Вопрос:", question.Text) + fmt.Println() + } + }, +} diff --git a/internal/ui/cli/question_adapter.go b/internal/ui/cli/question_adapter.go new file mode 100644 index 0000000..aee7e4d --- /dev/null +++ b/internal/ui/cli/question_adapter.go @@ -0,0 +1,25 @@ +package cli + +import ( + "fmt" + + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" +) + +type QuestionAdapter struct{} + +func (QuestionAdapter) ShowQuestion(question domain.Question) { + fmt.Println("Категория:", question.Category) + fmt.Println("Вопрос:", question.Text) +} + +func (QuestionAdapter) ReadAnswer() (int, error) { + var answer int + _, err := fmt.Scanln(&answer) + + return answer, err +} + +func (QuestionAdapter) ShowValidationError(err error) { + fmt.Println("Ошибка:", err) +} diff --git a/internal/ui/cli/root.go b/internal/ui/cli/root.go new file mode 100644 index 0000000..106d53d --- /dev/null +++ b/internal/ui/cli/root.go @@ -0,0 +1,43 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cli + +import ( + "database/sql" + + "github.com/spf13/cobra" +) + +var ( + db *sql.DB + period int +) + +var rootCmd = &cobra.Command{ + Use: "MyDailyControlQuestions", + Short: "Ежедневная саморефлексия путем ответа на важные для тебя вопросы по 5-ти бальной шкале", + Long: ``, +} + +func Execute(conn *sql.DB) { + db = conn + + // get stats answer + statsCmd.Flags().IntVarP(&period, "period", "p", 1, "Stats over the period") + statsCmd.MarkFlagRequired("stats") + + // get list question + rootCmd.AddCommand(listQuestionsCmd) + + // run survey + rootCmd.AddCommand(askQuestionsCmd) + + if err := rootCmd.Execute(); err != nil { + panic(err) + } +} + +func init() { + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/internal/ui/cli/stats.go b/internal/ui/cli/stats.go new file mode 100644 index 0000000..5a2a11a --- /dev/null +++ b/internal/ui/cli/stats.go @@ -0,0 +1,44 @@ +/* +Copyright © 2026 NAME HERE +*/ +package cli + +import ( + "fmt" + "log" + + "github.com/spf13/cobra" + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + "github.com/tulashvili/MyDailyControlQuestions/internal/repo" + "github.com/tulashvili/MyDailyControlQuestions/internal/usecase" +) + +// statsCmd represents the stats command +var statsCmd = &cobra.Command{ + Use: "stats", + Short: "Получить статистику ответов за Х дней", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + repo := repo.NewSQLiteAnswerRepository(db) + stats, err := usecase.NewGetStat(repo).Execute(period) + if err != nil { + log.Fatal(err) + } + for _, answer := range stats { + formatedPrintResult(answer) + } + }, +} + +func init() { + rootCmd.AddCommand(statsCmd) +} + +func formatedPrintResult(userAnswer domain.UserAnswer) { + fmt.Println("---------------------") + fmt.Println("ID:", userAnswer.ID) + fmt.Println("Дата:", userAnswer.AnsweredAt) + fmt.Println("Категория:", userAnswer.QuestionCategory) + fmt.Println("Вопрос:", userAnswer.QuestionText) + fmt.Println("Ответ:", userAnswer.Answer) +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go deleted file mode 100644 index 5c5e3ef..0000000 --- a/internal/ui/ui.go +++ /dev/null @@ -1,66 +0,0 @@ -package ui - -import ( - "database/sql" - "fmt" - - "github.com/tulashvili/MyDailyControlQuestions/internal/models" - "github.com/tulashvili/MyDailyControlQuestions/internal/service" - "github.com/tulashvili/MyDailyControlQuestions/internal/storage" -) - -// Get question list with category -func GetQuestions(questions []models.Question) { - for _, question := range questions { - fmt.Println("Категория:", question.Category) - fmt.Println("Вопрос:", question.Text) - } -} - -// Ask a question to a client -func AskQuestion(questions []models.Question) []models.UserAnswer { - var result []models.UserAnswer - - for _, question := range questions { - fmt.Println("Категория:", question.Category) - fmt.Println("Вопрос:", question.Text) - - var answer int - fmt.Scanln(&answer) - - err := service.ValidateAnswer(answer) - if err != nil { - fmt.Println(err) - fmt.Println("Введи значение заново") - fmt.Scanln(&answer) - } else { - userAnswer := service.CreateUserAnswer(question, answer) - result = append(result, userAnswer) - } - } - return result -} - -// Show data over period -func ShowDataOverPeriod(conn *sql.DB) error { - period := 2 - rows, err := storage.SelectRows(conn, period) - if err != nil { - return err - } - - for _, row := range rows { - formatedPrintResult(row) - } - return nil -} - -// Formated result from SelectRow -func formatedPrintResult(userAnswer storage.UserAnswerRow) { - fmt.Println("---------------------") - fmt.Println("ID:", userAnswer.ID) - fmt.Println("Дата:", userAnswer.AnsweredAt) - fmt.Println("Категория:", userAnswer.Category) - fmt.Println("Вопрос:", userAnswer.Question) - fmt.Println("Ответ:", userAnswer.Answer) -} diff --git a/internal/usecase/ask_question.go b/internal/usecase/ask_question.go new file mode 100644 index 0000000..739f87b --- /dev/null +++ b/internal/usecase/ask_question.go @@ -0,0 +1,40 @@ +package usecase + +import ( + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + "github.com/tulashvili/MyDailyControlQuestions/internal/repo" +) + +type AskQuestionUC struct { + repo repo.AnswerRepository +} + +func NewAskQuestion(repo repo.AnswerRepository) *AskQuestionUC { + return &AskQuestionUC{repo: repo} +} + +func (aq *AskQuestionUC) Execute(questions []domain.Question, io QuestionIO) error { + for _, q := range questions { + for { + io.ShowQuestion(q) + + answer, err := io.ReadAnswer() + if err != nil { + return err + } + + if err := domain.ValidateAnswer(answer); err != nil { + io.ShowValidationError(err) + } + + userAnswer := domain.CreateUserAnswer(q, answer) + + if err := aq.repo.SaveAnswer(userAnswer); err != nil { + return err + } + + break + } + } + return nil +} diff --git a/internal/usecase/get_stats.go b/internal/usecase/get_stats.go new file mode 100644 index 0000000..a63d6cc --- /dev/null +++ b/internal/usecase/get_stats.go @@ -0,0 +1,18 @@ +package usecase + +import ( + "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + "github.com/tulashvili/MyDailyControlQuestions/internal/repo" +) + +type GetStatAnswer struct { + repo repo.AnswerRepository +} + +func NewGetStat(repo repo.AnswerRepository) *GetStatAnswer { + return &GetStatAnswer{repo: repo} +} + +func (gs *GetStatAnswer) Execute(period int) ([]domain.UserAnswer, error) { + return gs.repo.GetAnswers(period) +} diff --git a/internal/usecase/list_questions.go b/internal/usecase/list_questions.go new file mode 100644 index 0000000..afdeef2 --- /dev/null +++ b/internal/usecase/list_questions.go @@ -0,0 +1,8 @@ +package usecase + +import "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + +// Return question list with category +func GetQuestionList() []domain.Question { + return domain.QUESTIONS +} diff --git a/internal/usecase/question.go b/internal/usecase/question.go new file mode 100644 index 0000000..6225cc1 --- /dev/null +++ b/internal/usecase/question.go @@ -0,0 +1,9 @@ +package usecase + +import "github.com/tulashvili/MyDailyControlQuestions/internal/domain" + +type QuestionIO interface { + ShowQuestion(domain.Question) + ReadAnswer() (int, error) + ShowValidationError(error) +} \ No newline at end of file diff --git a/pkg/db/datasource.go b/pkg/db/datasource.go new file mode 100644 index 0000000..f8cf5f3 --- /dev/null +++ b/pkg/db/datasource.go @@ -0,0 +1,15 @@ +package db + +import ( + "os" +) + +type DataSource struct { + SqlitePath string +} + +func LoadDatasource() DataSource { + return DataSource{ + SqlitePath: os.Getenv("DB_PATH"), + } +} diff --git a/pkg/db/sqlitedb/sqlite.go b/pkg/db/sqlitedb/sqlite.go new file mode 100644 index 0000000..fee0c9f --- /dev/null +++ b/pkg/db/sqlitedb/sqlite.go @@ -0,0 +1,40 @@ +package sqlitedb + +import ( + "database/sql" + "log/slog" + + _ "github.com/mattn/go-sqlite3" + "github.com/tulashvili/MyDailyControlQuestions/pkg/db" +) + +// ToDo: move this to migrate.go + +func InitDB(path db.DataSource) (*sql.DB, error) { + conn, err := sql.Open("sqlite3", path.SqlitePath) + if err == nil { + slog.Info( + "Соединение с базой установлено", + "sqlite_path", path.SqlitePath, + ) + } + return conn, err + +} + +func CreateTable(conn *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS daily_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + question TEXT NOT NULL, + answer INTEGER NOT NULL, + answeredAt TEXT NOT NULL + ); + ` + _, err := conn.Exec(query) + if err == nil { + slog.Info("Таблица daily_log готова к использованию") + } + return err +}