From ab3f6d431336f029a02119939a33076d3198f463 Mon Sep 17 00:00:00 2001 From: lafetz Date: Wed, 26 Mar 2025 23:50:19 +0300 Subject: [PATCH] init --- .air.toml | 46 ++++++++ .gitignore | 6 ++ cmd/main.go | 5 + compose.db.yaml | 37 +++++++ db/migrations/000001_fortune.down.sql | 74 +++++++++++++ db/migrations/000001_fortune.up.sql | 12 +++ db/query/user.sql | 16 +++ gen/db/db.go | 32 ++++++ gen/db/models.go | 22 ++++ gen/db/user.sql.go | 149 ++++++++++++++++++++++++++ go.mod | 14 +++ go.sum | 28 +++++ internal/domain/auth.go | 1 + internal/domain/user.go | 12 +++ internal/repository/store.go | 41 +++++++ internal/repository/user.go | 81 ++++++++++++++ internal/services/user/port.go | 15 +++ internal/services/user/service.go | 33 ++++++ internal/web_server/server.go | 76 +++++++++++++ makefile | 32 ++++++ sqlc.yaml | 18 ++++ 21 files changed, 750 insertions(+) create mode 100644 .air.toml create mode 100644 .gitignore create mode 100644 cmd/main.go create mode 100644 compose.db.yaml create mode 100644 db/migrations/000001_fortune.down.sql create mode 100644 db/migrations/000001_fortune.up.sql create mode 100644 db/query/user.sql create mode 100644 gen/db/db.go create mode 100644 gen/db/models.go create mode 100644 gen/db/user.sql.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/domain/auth.go create mode 100644 internal/domain/user.go create mode 100644 internal/repository/store.go create mode 100644 internal/repository/user.go create mode 100644 internal/services/user/port.go create mode 100644 internal/services/user/service.go create mode 100644 internal/web_server/server.go create mode 100644 makefile create mode 100644 sqlc.yaml diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..a540c10 --- /dev/null +++ b/.air.toml @@ -0,0 +1,46 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "./tmp/main" +cmd = "go build -o ./tmp/main ./cmd" +delay = 1000 +exclude_dir = ["assets", "tmp", "vendor", "testdata"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +full_bin = "" +include_dir = [] +include_ext = ["go", "tpl", "tmpl", "html"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +post_cmd = [] +pre_cmd = [] +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = false + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +time = false + +[misc] +clean_on_exit = false + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c29bfa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin +coverage.out +coverage +.env +tmp +build diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..7905807 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/compose.db.yaml b/compose.db.yaml new file mode 100644 index 0000000..72ab8a7 --- /dev/null +++ b/compose.db.yaml @@ -0,0 +1,37 @@ +services: + postgres: + image: postgres:16-alpine + ports: + - 5422:5432 + environment: + - POSTGRES_PASSWORD=secret + - POSTGRES_USER=root + - POSTGRES_DB=gh + networks: + - app + healthcheck: + test: ["CMD-SHELL", "pg_isready -U root -d gh"] + interval: 5s + timeout: 3s + retries: 5 + migrate: + image: migrate/migrate + volumes: + - ./db/migrations:/migrations + depends_on: + postgres: + condition: service_healthy + command: + [ + "-path", + "/migrations", + "-database", + "postgresql://root:secret@postgres:5432/gh?sslmode=disable", + "up", + ] + networks: + - app + +networks: + app: + driver: bridge diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql new file mode 100644 index 0000000..489466f --- /dev/null +++ b/db/migrations/000001_fortune.down.sql @@ -0,0 +1,74 @@ +-- Drop tables that depend on service_type_setting +DROP TABLE IF EXISTS service_type_setting; + +-- Drop product-related tables and types +DROP TABLE IF EXISTS product; +DROP TYPE IF EXISTS tier_group; + +-- Drop onboarding-related tables and types +DROP TABLE IF EXISTS verification_key; +DROP TABLE IF EXISTS onboarding_user; +DROP TYPE IF EXISTS verification_status; +DROP TYPE IF EXISTS onboarding_status; + +-- Drop staff-related tables and types +DROP TABLE IF EXISTS staff_session; +DROP TABLE IF EXISTS user_agent; +DROP TABLE IF EXISTS staff; +DROP TYPE IF EXISTS password_status; + +-- Drop mobile app-related tables and types +DROP TABLE IF EXISTS user_devices; +DROP TABLE IF EXISTS user_session; +DROP TABLE IF EXISTS linked_accounts; +DROP TABLE IF EXISTS users; +DROP TYPE IF EXISTS device_type; +DROP TYPE IF EXISTS registeration_type; +DROP TYPE IF EXISTS customer_group; + +-- Drop linked accounts and beneficiary tables and types +DROP TABLE IF EXISTS beneficiary; +DROP TYPE IF EXISTS fund_destination; + +-- Drop maker checker-related tables and types +DROP TABLE IF EXISTS workflow; +DROP TYPE IF EXISTS approval_status; +DROP TYPE IF EXISTS action_type; +DROP TYPE IF EXISTS context_type; + +-- Drop authorization-related tables and types +DROP TRIGGER IF EXISTS enforce_unique_array ON policy; +DROP FUNCTION IF EXISTS check_unique_array; +DROP TABLE IF EXISTS policy; +DROP TABLE IF EXISTS roles; +DROP TYPE IF EXISTS policy_action; +DROP TYPE IF EXISTS policy_object; + +-- Drop bank-related tables and types +DROP TABLE IF EXISTS bank; +DROP TABLE IF EXISTS flagged_users; + +-- Drop transaction-related tables and types +DROP TABLE IF EXISTS transaction_daily; +DROP TABLE IF EXISTS system_limits; +DROP TABLE IF EXISTS transactions; +DROP TYPE IF EXISTS payment_status; +DROP TYPE IF EXISTS service_type; +DROP TYPE IF EXISTS channel; +DROP TYPE IF EXISTS transaction_category; +DROP TYPE IF EXISTS registration_type; + +-- Drop branches and related tables +DROP TABLE IF EXISTS branches; +DROP TABLE IF EXISTS cities; +DROP TABLE IF EXISTS districts; +DROP TABLE IF EXISTS regions; + +-- Drop activity logs +DROP TABLE IF EXISTS activity; + +-- Drop ussd account and related enums +DROP TABLE IF EXISTS ussd_account; +DROP TYPE IF EXISTS ua_pin_status; +DROP TYPE IF EXISTS ua_status; +DROP TYPE IF EXISTS ua_registaration_type; diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql new file mode 100644 index 0000000..bfd54da --- /dev/null +++ b/db/migrations/000001_fortune.up.sql @@ -0,0 +1,12 @@ +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + phone_number VARCHAR(20) UNIQUE NOT NULL, + password TEXT NOT NULL, + role VARCHAR(50) NOT NULL, + verified BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP , + updated_at TIMESTAMP +); \ No newline at end of file diff --git a/db/query/user.sql b/db/query/user.sql new file mode 100644 index 0000000..1d356e9 --- /dev/null +++ b/db/query/user.sql @@ -0,0 +1,16 @@ +-- name: CreateUser :one +INSERT INTO users (first_name, last_name, email, phone_number, password, role, verified) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING *; + +-- name: GetUserByID :one +SELECT * FROM users WHERE id = $1; + +-- name: GetAllUsers :many +SELECT * FROM users; + +-- name: UpdateUser :exec +UPDATE users SET first_name = $2, last_name = $3, email = $4, phone_number = $5, password = $6, role = $7, verified = $8, updated_at = CURRENT_TIMESTAMP WHERE id = $1; + +-- name: DeleteUser :exec +DELETE FROM users WHERE id = $1; diff --git a/gen/db/db.go b/gen/db/db.go new file mode 100644 index 0000000..fe4ae38 --- /dev/null +++ b/gen/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/gen/db/models.go b/gen/db/models.go new file mode 100644 index 0000000..307326d --- /dev/null +++ b/gen/db/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package dbgen + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type User struct { + ID int64 + FirstName string + LastName string + Email string + PhoneNumber string + Password string + Role string + Verified pgtype.Bool + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go new file mode 100644 index 0000000..1608a32 --- /dev/null +++ b/gen/db/user.sql.go @@ -0,0 +1,149 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: user.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const CreateUser = `-- name: CreateUser :one +INSERT INTO users (first_name, last_name, email, phone_number, password, role, verified) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at +` + +type CreateUserParams struct { + FirstName string + LastName string + Email string + PhoneNumber string + Password string + Role string + Verified pgtype.Bool +} + +func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) { + row := q.db.QueryRow(ctx, CreateUser, + arg.FirstName, + arg.LastName, + arg.Email, + arg.PhoneNumber, + arg.Password, + arg.Role, + arg.Verified, + ) + var i User + err := row.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Password, + &i.Role, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const DeleteUser = `-- name: DeleteUser :exec +DELETE FROM users WHERE id = $1 +` + +func (q *Queries) DeleteUser(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, DeleteUser, id) + return err +} + +const GetAllUsers = `-- name: GetAllUsers :many +SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users +` + +func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) { + rows, err := q.db.Query(ctx, GetAllUsers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Password, + &i.Role, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserByID = `-- name: GetUserByID :one +SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users WHERE id = $1 +` + +func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { + row := q.db.QueryRow(ctx, GetUserByID, id) + var i User + err := row.Scan( + &i.ID, + &i.FirstName, + &i.LastName, + &i.Email, + &i.PhoneNumber, + &i.Password, + &i.Role, + &i.Verified, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateUser = `-- name: UpdateUser :exec +UPDATE users SET first_name = $2, last_name = $3, email = $4, phone_number = $5, password = $6, role = $7, verified = $8, updated_at = CURRENT_TIMESTAMP WHERE id = $1 +` + +type UpdateUserParams struct { + ID int64 + FirstName string + LastName string + Email string + PhoneNumber string + Password string + Role string + Verified pgtype.Bool +} + +func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error { + _, err := q.db.Exec(ctx, UpdateUser, + arg.ID, + arg.FirstName, + arg.LastName, + arg.Email, + arg.PhoneNumber, + arg.Password, + arg.Role, + arg.Verified, + ) + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..70aa95d --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/SamuelTariku/FortuneBet-Backend + +go 1.24.1 + +require github.com/jackc/pgx/v5 v5.7.4 + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa0f7db --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/domain/auth.go b/internal/domain/auth.go new file mode 100644 index 0000000..4188b5a --- /dev/null +++ b/internal/domain/auth.go @@ -0,0 +1 @@ +package domain diff --git a/internal/domain/user.go b/internal/domain/user.go new file mode 100644 index 0000000..1e6b181 --- /dev/null +++ b/internal/domain/user.go @@ -0,0 +1,12 @@ +package domain + +type User struct { + ID int64 + FirstName string + LastName string + Email string + PhoneNumber string + Password string + Role string + Verified bool +} diff --git a/internal/repository/store.go b/internal/repository/store.go new file mode 100644 index 0000000..02e1403 --- /dev/null +++ b/internal/repository/store.go @@ -0,0 +1,41 @@ +package repository + +import ( + "context" + "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/jackc/pgx/v5/pgxpool" +) + +type Store struct { + queries *dbgen.Queries + conn *pgxpool.Pool +} + +func NewStore(conn *pgxpool.Pool) *Store { + + queries := dbgen.New(conn) + return &Store{ + queries: queries, + conn: conn, + } +} + +func OpenDB(url string) (*pgxpool.Pool, func(), error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + conn, err := pgxpool.New(ctx, url) + if err != nil { + return nil, func() {}, err + } + + if err := conn.Ping(ctx); err != nil { + return nil, func() {}, err + } + + return conn, func() { + conn.Close() + }, nil +} diff --git a/internal/repository/user.go b/internal/repository/user.go new file mode 100644 index 0000000..b19798f --- /dev/null +++ b/internal/repository/user.go @@ -0,0 +1,81 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +func (s *Store) CreateUser(ctx context.Context, firstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error) { + user, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ + FirstName: firstName, + LastName: lastName, + Email: email, + PhoneNumber: phoneNumber, + Password: password, + Role: role, + }) + if err != nil { + return domain.User{}, err + } + return domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Password: user.Password, + Role: user.Role, + }, nil + +} +func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) { + user, err := s.queries.GetUserByID(ctx, id) + if err != nil { + return domain.User{}, err + } + return domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Password: user.Password, + Role: user.Role, + }, nil +} +func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) { + users, err := s.queries.GetAllUsers(ctx) + if err != nil { + return nil, err + } + var result []domain.User + for _, user := range users { + result = append(result, domain.User{ + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + Email: user.Email, + PhoneNumber: user.PhoneNumber, + Password: user.Password, + Role: user.Role, + }) + } + return result, nil +} +func (s *Store) UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error { + err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ + ID: id, + FirstName: firstName, + LastName: lastName, + Email: email, + PhoneNumber: phoneNumber, + Password: password, + Role: role, + }) + return err +} +func (s *Store) DeleteUser(ctx context.Context, id int64) error { + return s.queries.DeleteUser(ctx, id) +} diff --git a/internal/services/user/port.go b/internal/services/user/port.go new file mode 100644 index 0000000..34700ad --- /dev/null +++ b/internal/services/user/port.go @@ -0,0 +1,15 @@ +package user + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type UserStore interface { + CreateUser(ctx context.Context, CfirstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error) + GetUserByID(ctx context.Context, id int64) (domain.User, error) + GetAllUsers(ctx context.Context) ([]domain.User, error) + UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error + DeleteUser(ctx context.Context, id int64) error +} diff --git a/internal/services/user/service.go b/internal/services/user/service.go new file mode 100644 index 0000000..d427b66 --- /dev/null +++ b/internal/services/user/service.go @@ -0,0 +1,33 @@ +package user + +import ( + "context" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + userStore UserStore +} + +func NewService(userStore UserStore) *Service { + return &Service{ + userStore: userStore, + } +} + +func (s *Service) CreateUser(ctx context.Context, firstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error) { + return s.userStore.CreateUser(ctx, firstName, lastName, email, phoneNumber, password, role, verified) +} +func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) { + return s.userStore.GetUserByID(ctx, id) +} +func (s *Service) GetAllUsers(ctx context.Context) ([]domain.User, error) { + return s.userStore.GetAllUsers(ctx) +} +func (s *Service) UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error { + return s.userStore.UpdateUser(ctx, id, firstName, lastName, email, phoneNumber, password, role, verified) +} +func (s *Service) DeleteUser(ctx context.Context, id int64) error { + return s.userStore.DeleteUser(ctx, id) +} diff --git a/internal/web_server/server.go b/internal/web_server/server.go new file mode 100644 index 0000000..944f397 --- /dev/null +++ b/internal/web_server/server.go @@ -0,0 +1,76 @@ +package httpserver + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +type App struct { + port int + Router *http.ServeMux + logger *slog.Logger + notificationSecret string +} + +func NewApp( + port int, logger *slog.Logger, + notificationSecret string, + +) *App { + a := &App{ + Router: http.NewServeMux(), + logger: logger, + port: port, + notificationSecret: notificationSecret, + } + a.initAppRoutes() + return a +} +func (a *App) Run() error { + + srv := &http.Server{ + Addr: fmt.Sprintf(":%s", strconv.Itoa(a.port)), + Handler: a.Router, + IdleTimeout: time.Minute, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + } + + shutdownError := make(chan error) + go func() { + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + <-quit + + a.logger.Info("shutting down server") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + shutdownError <- srv.Shutdown(ctx) + }() + err := srv.ListenAndServe() + + if !errors.Is(err, http.ErrServerClosed) { + return err + } + + err = <-shutdownError + if err != nil { + return err + } + a.logger.Info("server stopped") + return nil +} + +func (a *App) initAppRoutes() { + // a.Router.HandleFunc("/users",) +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..845b5e7 --- /dev/null +++ b/makefile @@ -0,0 +1,32 @@ +include .env +.PHONY: test +test: + go test ./app +.PHONY: coverage +coverage: + mkdir -p coverage + go test -coverprofile=coverage.out ./internal/... ; + go tool cover -func=coverage.out -o coverage/coverage.txt +.PHONY: build +build: + go build -ldflags="-s" -o ./bin/web ./ +.PHONY: run +run: + echo "Running Go application"; \ + go run ./cmd/main.go +.PHONY: air +air: + echo "Running air"; \ + air -c .air.toml +.PHONY: migrations/up +migrations/new: + @echo 'Creating migration files for DB_URL' + migrate create -seq -ext=.sql -dir=./db/migrations $(name) +.PHONY: migrations/up +migrations/up: + @echo 'Running up migrations...' + migrate -path ./db/migrations -database $(DB_URL) up + +.PHONY: swagger +swagger: + swag init -g cmd/main.go diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..6f394a6 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,18 @@ +version: "2" +sql: + - schema: "./db/migrations/" + queries: "./db/query/" + engine: "postgresql" + gen: + go: + package: "dbgen" + sql_package: "pgx/v5" + out: "./gen/db" + emit_exported_queries: true + emit_json_tags: false + overrides: + - db_type: "uuid" + go_type: "github.com/google/uuid.UUID" + - db_type: "uuid" + go_type: "github.com/google/uuid.NullUUID" + nullable: true