-
Notifications
You must be signed in to change notification settings - Fork 0
Fix/9 architecture #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9d536ee
e161b67
171f765
45564aa
dc59848
96f5eca
be75103
6183ff2
d0c6f85
9ef65e0
d995940
8501b6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,5 @@ | ||
| *.json | ||
| *.db | ||
| data/ | ||
| .DS_Store | ||
| .env | ||
| .vscode |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package domain | ||
|
|
||
| import "time" | ||
|
|
||
| type UserAnswer struct { | ||
| ID int | ||
| QuestionCategory string | ||
| QuestionText string | ||
| Answer int | ||
| AnsweredAt *time.Time | ||
| } | ||
|
Comment on lines
+5
to
+11
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужно тегирование полей |
||
|
|
||
| // 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, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,18 @@ | ||
| package service | ||
| package domain | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "time" | ||
| type Question struct { | ||
| Category | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Category: Category |
||
| Text string | ||
| } | ||
|
|
||
| "github.com/tulashvili/MyDailyControlQuestions/internal/models" | ||
| ) | ||
| type Category string | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В отдельный файл |
||
|
|
||
| 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, | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package domain | ||
|
|
||
| import "errors" | ||
|
|
||
| // Validate user answer | ||
| func ValidateAnswer(answer int) error { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Сделать его методом для UserAnswer, вынести его в файл answer.go |
||
| if answer < 1 || answer > 5 { | ||
| return errors.New("answer must be between 1 and 5") | ||
| } | ||
| return nil | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| } | ||
|
Comment on lines
+1
to
+8
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. тут это не нужно, за хорошую практику считается использовать интерфейсы в месте использования |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. убери из названия SQLite |
||
| 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 (?, ?, ?, ?) | ||
| ` | ||
|
Comment on lines
+21
to
+24
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. переписать под sqlx и почитай про sql инъекции |
||
| _, 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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. так же переписать весь метод под sqlx |
||
| 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 | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а где все остальное? слой репозитория, сервисный слой и т.д