Renamed and updated project.
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"GoFinanceManager/src/api/dto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Hello GoDoc
|
||||
// @Summary Say hello
|
||||
// @Description Returns greeting
|
||||
// @Tags hello
|
||||
// @Accept JSON
|
||||
// @Produce JSON
|
||||
// @Param name query string true "User name"
|
||||
// @Success 200 {object} dto.HelloResponse
|
||||
// @Failure 400 {object} dto.ErrorResponse
|
||||
// @Router /hello [get]
|
||||
func Hello(c *gin.Context) {
|
||||
var req dto.HelloRequest
|
||||
|
||||
// биндинг + валидация
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, dto.ErrorResponse{
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp := dto.HelloResponse{
|
||||
Message: "Hello " + req.Name,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"GoFinanceManager/src/api/dto"
|
||||
"GoFinanceManager/src/integrations/receiptService"
|
||||
"GoFinanceManager/src/utils"
|
||||
"FamilyHub/src/api/dto"
|
||||
"FamilyHub/src/integrations/receiptService"
|
||||
"FamilyHub/src/utils"
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -20,30 +20,26 @@ func NewReceiptHandler(s *receiptService.ReceiptService) *ReceiptHandler {
|
||||
return &ReceiptHandler{service: s}
|
||||
}
|
||||
|
||||
func (h *ReceiptHandler) AddReceipt(c *gin.Context) {
|
||||
func (handler *ReceiptHandler) AddReceipt(context_ *gin.Context) {
|
||||
var req dto.AddReceiptRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
if err := context_.ShouldBindJSON(&req); err != nil {
|
||||
log.Println("bind error:", err)
|
||||
c.JSON(http.StatusBadRequest, dto.ErrorResponse{
|
||||
Message: err.Error(),
|
||||
})
|
||||
context_.JSON(http.StatusBadRequest, dto.ErrorResponse{Message: err.Error()})
|
||||
return
|
||||
}
|
||||
isoDate, err := utils.NormalizeDateToISO(req.Date)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "invalid date format"})
|
||||
context_.JSON(400, gin.H{"error": "invalid date format"})
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
receipt, err := h.service.GetReceipt(
|
||||
ctx,
|
||||
isoDate,
|
||||
req.Number,
|
||||
)
|
||||
|
||||
receipt, err := handler.service.GetReceipt(ctx, isoDate, req.Number)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "Cant get receipt"})
|
||||
log.Print(err.Error())
|
||||
context_.JSON(400, gin.H{"error": err.Error()})
|
||||
log.Printf("API error, %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,5 +48,5 @@ func (h *ReceiptHandler) AddReceipt(c *gin.Context) {
|
||||
Number: receipt.ReceiptNumber,
|
||||
Date: receipt.IssuedAt,
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
context_.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,11 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"GoFinanceManager/src/api/handlers"
|
||||
"GoFinanceManager/src/config"
|
||||
"GoFinanceManager/src/database"
|
||||
"GoFinanceManager/src/integrations/receiptService"
|
||||
"GoFinanceManager/src/repositories"
|
||||
"FamilyHub/src/api/handlers"
|
||||
"FamilyHub/src/config"
|
||||
"FamilyHub/src/database"
|
||||
"FamilyHub/src/integrations/receiptService"
|
||||
"FamilyHub/src/repositories"
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -19,12 +19,16 @@ type Server struct {
|
||||
|
||||
func NewServer(cfg config.Config) *Server {
|
||||
handler := gin.Default()
|
||||
dbConn, err := database.Connect(cfg.DBConnectionString)
|
||||
dbManager := &database.Database{
|
||||
ConnectionString: cfg.DBConnectionString,
|
||||
MigrationsPath: "file://migrations",
|
||||
}
|
||||
dbConn, err := dbManager.Connect()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := database.RunMigrations(dbConn); err != nil {
|
||||
if err := dbManager.RunMigrations(dbConn); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"GoFinanceManager/src/config"
|
||||
"GoFinanceManager/src/integrations/ocr"
|
||||
"GoFinanceManager/src/integrations/receiptApi"
|
||||
"FamilyHub/src/config"
|
||||
"FamilyHub/src/integrations/ocr"
|
||||
"FamilyHub/src/integrations/receiptApi"
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
+6
-5
@@ -1,8 +1,8 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"GoFinanceManager/src/integrations/receiptApi"
|
||||
"GoFinanceManager/src/utils"
|
||||
"FamilyHub/src/integrations/receiptApi"
|
||||
"FamilyHub/src/utils"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func (bot *Bot) handleMessage(msg *tgbotapi.Message) {
|
||||
println(msg.Text)
|
||||
switch msg.Text {
|
||||
case "/start":
|
||||
bot.handleStart(msg)
|
||||
@@ -26,7 +25,6 @@ func (bot *Bot) handleMessage(msg *tgbotapi.Message) {
|
||||
}
|
||||
|
||||
func (bot *Bot) handlePhoto(msg *tgbotapi.Message) {
|
||||
// Берём самое большое фото
|
||||
photo := msg.Photo[len(msg.Photo)-1]
|
||||
|
||||
file, err := bot.api.GetFile(tgbotapi.FileConfig{FileID: photo.FileID})
|
||||
@@ -68,9 +66,12 @@ func (bot *Bot) handlePhoto(msg *tgbotapi.Message) {
|
||||
Date: receiptMeta.Date,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
txt, err := utils.DecodeQR(imageBytes)
|
||||
println(txt)
|
||||
|
||||
err = bot.receiptApi.SendReceipt(ctx, payload)
|
||||
|
||||
reply := "📄 *Результат распознавания*\n\n"
|
||||
|
||||
+102
-7
@@ -2,22 +2,117 @@ package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func Connect(dsn string) (*sql.DB, error) {
|
||||
u, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
type Database struct {
|
||||
ConnectionString string
|
||||
MigrationsPath string
|
||||
MaxOpenConns int
|
||||
MaxIdleConns int
|
||||
}
|
||||
|
||||
func (d *Database) Connect() (*sql.DB, error) {
|
||||
u, _ := url.Parse(d.ConnectionString)
|
||||
if u == nil {
|
||||
return nil, errors.New("nil url")
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "sqlite":
|
||||
return connectSQLite(u)
|
||||
case "sqlite", "sqlite3":
|
||||
path := filepath.Join(u.Host, u.Path)
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("empty sqlite path")
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.MaxOpenConns == 0 {
|
||||
d.MaxOpenConns = 1
|
||||
}
|
||||
db.SetMaxOpenConns(d.MaxOpenConns)
|
||||
return db, nil
|
||||
|
||||
case "postgres", "postgresql":
|
||||
db, err := sql.Open("postgres", u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.MaxOpenConns == 0 {
|
||||
d.MaxOpenConns = 20
|
||||
}
|
||||
if d.MaxIdleConns == 0 {
|
||||
d.MaxIdleConns = 10
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(d.MaxOpenConns)
|
||||
db.SetMaxIdleConns(d.MaxIdleConns)
|
||||
return db, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported db scheme: %s", u.Scheme)
|
||||
return nil, fmt.Errorf("unsupported database scheme: %s", u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Database) RunMigrations(db *sql.DB) error {
|
||||
u, _ := url.Parse(d.ConnectionString)
|
||||
if u == nil {
|
||||
return errors.New("nil url")
|
||||
}
|
||||
|
||||
if db == nil {
|
||||
return errors.New("nil db")
|
||||
}
|
||||
|
||||
if d.MigrationsPath == "" {
|
||||
d.MigrationsPath = "file://migrations"
|
||||
}
|
||||
|
||||
var m *migrate.Migrate
|
||||
|
||||
switch u.Scheme {
|
||||
case "sqlite", "sqlite3":
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err = migrate.NewWithDatabaseInstance(d.MigrationsPath, "sqlite", driver)
|
||||
|
||||
case "postgres", "postgresql":
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err = migrate.NewWithDatabaseInstance(d.MigrationsPath, "postgres", driver)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported database scheme for migrations: %s", u.Scheme)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return fmt.Errorf("migration failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
)
|
||||
|
||||
func RunMigrations(db *sql.DB) error {
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://migrations",
|
||||
"sqlite",
|
||||
driver,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
)
|
||||
|
||||
type PostgresDB struct {
|
||||
MigrationsPath string
|
||||
MaxOpenConns int
|
||||
MaxIdleConns int
|
||||
}
|
||||
|
||||
// Connect открывает соединение с Postgres
|
||||
func (p *PostgresDB) Connect(u *url.URL) (*sql.DB, error) {
|
||||
db, err := sql.Open("postgres", u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.MaxOpenConns == 0 {
|
||||
p.MaxOpenConns = 20
|
||||
}
|
||||
if p.MaxIdleConns == 0 {
|
||||
p.MaxIdleConns = 10
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(p.MaxOpenConns)
|
||||
db.SetMaxIdleConns(p.MaxIdleConns)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// RunMigrations запускает миграции PostgreSQL
|
||||
func (p *PostgresDB) RunMigrations(db *sql.DB) error {
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create postgres driver: %w", err)
|
||||
}
|
||||
|
||||
if p.MigrationsPath == "" {
|
||||
p.MigrationsPath = "file://migrations"
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
p.MigrationsPath,
|
||||
"postgres",
|
||||
driver,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migrate instance: %w", err)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return fmt.Errorf("migration failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+41
-2
@@ -2,13 +2,23 @@ package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
)
|
||||
|
||||
func connectSQLite(u *url.URL) (*sql.DB, error) {
|
||||
type SQLiteDB struct {
|
||||
MigrationsPath string
|
||||
MaxOpenConns int
|
||||
}
|
||||
|
||||
func (s *SQLiteDB) Connect(u *url.URL) (*sql.DB, error) {
|
||||
path := filepath.Join(u.Host, u.Path)
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("empty sqlite path")
|
||||
@@ -24,6 +34,35 @@ func connectSQLite(u *url.URL) (*sql.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(1) // важно для sqlite
|
||||
if s.MaxOpenConns == 0 {
|
||||
s.MaxOpenConns = 1
|
||||
}
|
||||
db.SetMaxOpenConns(s.MaxOpenConns)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteDB) RunMigrations(db *sql.DB) error {
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.MigrationsPath == "" {
|
||||
s.MigrationsPath = "file://migrations"
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
s.MigrationsPath,
|
||||
"sqlite",
|
||||
driver,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
TelegramID int64
|
||||
Username *string
|
||||
FirstName string
|
||||
LastName *string
|
||||
LanguageCode *string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type TelegramChat struct {
|
||||
ID int64
|
||||
TelegramID int64
|
||||
Title string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type Family struct {
|
||||
ID int64
|
||||
Name string
|
||||
OwnerID int64
|
||||
TelegramChatID int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type FamilyRole string
|
||||
|
||||
const (
|
||||
FamilyRoleOwner FamilyRole = "owner"
|
||||
FamilyRoleAdmin FamilyRole = "admin"
|
||||
FamilyRoleMember FamilyRole = "member"
|
||||
FamilyRoleChild FamilyRole = "child"
|
||||
)
|
||||
|
||||
type FamilyMember struct {
|
||||
ID int64
|
||||
FamilyID int64
|
||||
UserID int64
|
||||
Role FamilyRole
|
||||
JoinedAt time.Time
|
||||
}
|
||||
|
||||
type ThreadType string
|
||||
|
||||
const (
|
||||
ThreadExpenses ThreadType = "expenses"
|
||||
ThreadMovies ThreadType = "movies"
|
||||
ThreadSchedule ThreadType = "schedule"
|
||||
ThreadRecipes ThreadType = "recipes"
|
||||
ThreadCustom ThreadType = "custom"
|
||||
)
|
||||
|
||||
type Thread struct {
|
||||
ID int64
|
||||
FamilyID int64
|
||||
Type ThreadType
|
||||
Title string
|
||||
TelegramTopicID int64
|
||||
IsSystem bool
|
||||
CreatedBy int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func NewHTTPClient(baseURL string) (*HTTPClient, error) {
|
||||
return &HTTPClient{
|
||||
baseURL: baseURL,
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: 60 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -44,9 +44,6 @@ func (c *HTTPClient) SendReceipt(
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
//if c.apiKey != "" {
|
||||
// req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
//}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package receiptService
|
||||
|
||||
import (
|
||||
"GoFinanceManager/src/domain/models"
|
||||
"GoFinanceManager/src/repositories"
|
||||
"GoFinanceManager/src/utils"
|
||||
"FamilyHub/src/domain/models"
|
||||
"FamilyHub/src/repositories"
|
||||
"FamilyHub/src/utils"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
@@ -23,7 +24,7 @@ type ReceiptService struct {
|
||||
func NewReceiptService(repo repositories.ReceiptRepository) *ReceiptService {
|
||||
return &ReceiptService{
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: 60 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
@@ -40,8 +41,8 @@ func (s *ReceiptService) GetReceipt(
|
||||
number string,
|
||||
) (*models.Receipt, error) {
|
||||
url := "https://ch.info-center.by/ajax/check1.php"
|
||||
|
||||
var receipt models.Receipt
|
||||
|
||||
body, contentType := buildMultipartBody(date, number)
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
@@ -71,26 +72,26 @@ func (s *ReceiptService) GetReceipt(
|
||||
var raw struct {
|
||||
Message map[string]interface{} `json:"message"`
|
||||
}
|
||||
log.Println(raw.Message)
|
||||
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
|
||||
log.Printf("external service returned %s\n", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw.Message["receipt_number"] = number
|
||||
|
||||
bytes_, _ := json.Marshal(raw.Message)
|
||||
|
||||
if err := json.Unmarshal(bytes_, &receipt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Println(receipt)
|
||||
|
||||
if receipt.IssuedAtRaw == "" {
|
||||
return nil, errors.New("receipt not found")
|
||||
}
|
||||
|
||||
positions, err := parsePositions(receipt.PositionsRaw)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse positions: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
log.Println(receipt.IssuedAtRaw)
|
||||
receipt.IssuedAt, err = utils.ParseIssuedAt(receipt.IssuedAtRaw)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse issued at: %s", err.Error())
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api"
|
||||
"FamilyHub/src/bot"
|
||||
"FamilyHub/src/config"
|
||||
|
||||
"context"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var runnable []Runnable
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if cfg.RunMode == config.API || cfg.RunMode == config.Standalone {
|
||||
server := api.NewServer(cfg)
|
||||
runnable = append(runnable, func(ctx context.Context) error {
|
||||
log.Println("API started on", cfg.APIPort)
|
||||
return server.Start()
|
||||
})
|
||||
runnable = append(runnable, func(ctx context.Context) error {
|
||||
<-ctx.Done()
|
||||
return server.Shutdown(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
if cfg.RunMode == config.Bot || cfg.RunMode == config.Standalone {
|
||||
tgBot, _ := bot.NewBot(cfg)
|
||||
log.Println("Bot started...")
|
||||
runnable = append(runnable, func(ctx context.Context) error {
|
||||
return tgBot.Start(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
Run(ctx, runnable...)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package repositories
|
||||
import (
|
||||
"context"
|
||||
|
||||
"GoFinanceManager/src/domain/models"
|
||||
"FamilyHub/src/domain/models"
|
||||
)
|
||||
|
||||
type ReceiptRepository interface {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"GoFinanceManager/src/domain/models"
|
||||
"FamilyHub/src/domain/models"
|
||||
)
|
||||
|
||||
type ReceiptSQLRepository struct {
|
||||
@@ -16,17 +16,16 @@ func NewReceiptSQLRepository(db *sql.DB) *ReceiptSQLRepository {
|
||||
return &ReceiptSQLRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Create(
|
||||
ctx context.Context,
|
||||
receipt *models.Receipt,
|
||||
) (int64, error) {
|
||||
func (r *ReceiptSQLRepository) Create(ctx context.Context, receipt *models.Receipt) (int64, error) {
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if receipt.ReceiptNumber != receipt.UI {
|
||||
receipt.ReceiptNumber = receipt.UI
|
||||
}
|
||||
res, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO receipts (
|
||||
receipt_number, ui, status, issued_at,
|
||||
@@ -130,10 +129,7 @@ func (r *ReceiptSQLRepository) Create(
|
||||
return receiptID, tx.Commit()
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) GetByID(
|
||||
ctx context.Context,
|
||||
id int64,
|
||||
) (*models.Receipt, error) {
|
||||
func (r *ReceiptSQLRepository) GetByID(ctx context.Context, id int64) (*models.Receipt, error) {
|
||||
|
||||
var receipt models.Receipt
|
||||
|
||||
@@ -234,10 +230,7 @@ func (r *ReceiptSQLRepository) GetByID(
|
||||
return &receipt, nil
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) GetAll(
|
||||
ctx context.Context,
|
||||
limit, offset int,
|
||||
) ([]*models.Receipt, error) {
|
||||
func (r *ReceiptSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*models.Receipt, error) {
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, receipt_number, issued_at, total_amount, currency
|
||||
@@ -269,10 +262,7 @@ func (r *ReceiptSQLRepository) GetAll(
|
||||
return receipts, nil
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Update(
|
||||
ctx context.Context,
|
||||
receipt *models.Receipt,
|
||||
) error {
|
||||
func (r *ReceiptSQLRepository) Update(ctx context.Context, receipt *models.Receipt) error {
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
@@ -315,10 +305,7 @@ func (r *ReceiptSQLRepository) Update(
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Delete(
|
||||
ctx context.Context,
|
||||
id int64,
|
||||
) error {
|
||||
func (r *ReceiptSQLRepository) Delete(ctx context.Context, id int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
`DELETE FROM receipts WHERE id = ?`,
|
||||
id,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Runnable func(ctx context.Context) error
|
||||
|
||||
func Run(ctx context.Context, runnable ...Runnable) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
errCh := make(chan error, len(runnable))
|
||||
|
||||
for _, r := range runnable {
|
||||
go func(run Runnable) {
|
||||
if err := run(ctx); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}(r)
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case sig := <-sigCh:
|
||||
log.Println("shutdown signal:", sig)
|
||||
case err := <-errCh:
|
||||
log.Println("runtime error:", err)
|
||||
}
|
||||
|
||||
cancel()
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -21,7 +20,6 @@ var knownDateFormats = []string{
|
||||
|
||||
func NormalizeDateToISO(input string) (string, error) {
|
||||
input = strings.TrimSpace(input)
|
||||
log.Println(input)
|
||||
for _, layout := range knownDateFormats {
|
||||
if t, err := time.Parse(layout, input); err == nil {
|
||||
return t.Format("2006-01-02"), nil
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/liyue201/goqr"
|
||||
)
|
||||
|
||||
func DecodeQR(imageBytes []byte) ([]string, error) {
|
||||
img, _, err := image.Decode(bytes.NewReader(imageBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
codes, err := goqr.Recognize(img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
println(codes)
|
||||
|
||||
var result []string
|
||||
for _, code := range codes {
|
||||
result = append(result, string(code.Payload))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user