- Cria README.md na raiz com visão global e diagrama de arquitetura - Adiciona/atualiza README.md em todos os componentes: - backend (API Go) - backoffice (NestJS) - marketplace (React/Vite) - saveinmed-bff (Python/FastAPI) - saveinmed-frontend (Next.js) - website (Fresh/Deno) - Atualiza .gitignore em todos os componentes com regras abrangentes - Cria .gitignore na raiz do projeto - Renomeia pastas para melhor organização: - backend-go → backend - backend-nest → backoffice - marketplace-front → marketplace - Documenta arquitetura, tecnologias, setup e fluxo de desenvolvimento
181 lines
5.5 KiB
Go
181 lines
5.5 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
"github.com/saveinmed/backend-go/internal/domain"
|
|
)
|
|
|
|
// Repository implements the data access layer using sqlx + pgx.
|
|
type Repository struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// New creates a Postgres-backed repository and configures pooling.
|
|
func New(db *sqlx.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
func (r *Repository) CreateCompany(ctx context.Context, company *domain.Company) error {
|
|
now := time.Now().UTC()
|
|
company.CreatedAt = now
|
|
company.UpdatedAt = now
|
|
|
|
query := `INSERT INTO companies (id, role, cnpj, corporate_name, sanitary_license, created_at, updated_at)
|
|
VALUES (:id, :role, :cnpj, :corporate_name, :sanitary_license, :created_at, :updated_at)`
|
|
|
|
_, err := r.db.NamedExecContext(ctx, query, company)
|
|
return err
|
|
}
|
|
|
|
func (r *Repository) ListCompanies(ctx context.Context) ([]domain.Company, error) {
|
|
var companies []domain.Company
|
|
query := `SELECT id, role, cnpj, corporate_name, sanitary_license, created_at, updated_at FROM companies ORDER BY created_at DESC`
|
|
if err := r.db.SelectContext(ctx, &companies, query); err != nil {
|
|
return nil, err
|
|
}
|
|
return companies, nil
|
|
}
|
|
|
|
func (r *Repository) CreateProduct(ctx context.Context, product *domain.Product) error {
|
|
now := time.Now().UTC()
|
|
product.CreatedAt = now
|
|
product.UpdatedAt = now
|
|
|
|
query := `INSERT INTO products (id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at)
|
|
VALUES (:id, :seller_id, :name, :description, :batch, :expires_at, :price_cents, :stock, :created_at, :updated_at)`
|
|
|
|
_, err := r.db.NamedExecContext(ctx, query, product)
|
|
return err
|
|
}
|
|
|
|
func (r *Repository) ListProducts(ctx context.Context) ([]domain.Product, error) {
|
|
var products []domain.Product
|
|
query := `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products ORDER BY created_at DESC`
|
|
if err := r.db.SelectContext(ctx, &products, query); err != nil {
|
|
return nil, err
|
|
}
|
|
return products, nil
|
|
}
|
|
|
|
func (r *Repository) CreateOrder(ctx context.Context, order *domain.Order) error {
|
|
now := time.Now().UTC()
|
|
order.CreatedAt = now
|
|
order.UpdatedAt = now
|
|
|
|
tx, err := r.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
orderQuery := `INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`
|
|
if _, err := tx.ExecContext(ctx, orderQuery, order.ID, order.BuyerID, order.SellerID, order.Status, order.TotalCents, order.CreatedAt, order.UpdatedAt); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
itemQuery := `INSERT INTO order_items (id, order_id, product_id, quantity, unit_cents, batch, expires_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`
|
|
for i := range order.Items {
|
|
item := &order.Items[i]
|
|
item.ID = uuid.Must(uuid.NewV7())
|
|
item.OrderID = order.ID
|
|
if _, err := tx.ExecContext(ctx, itemQuery, item.ID, item.OrderID, item.ProductID, item.Quantity, item.UnitCents, item.Batch, item.ExpiresAt); err != nil {
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
|
|
var order domain.Order
|
|
orderQuery := `SELECT id, buyer_id, seller_id, status, total_cents, created_at, updated_at FROM orders WHERE id = $1`
|
|
if err := r.db.GetContext(ctx, &order, orderQuery, id); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var items []domain.OrderItem
|
|
itemQuery := `SELECT id, order_id, product_id, quantity, unit_cents, batch, expires_at FROM order_items WHERE order_id = $1`
|
|
if err := r.db.SelectContext(ctx, &items, itemQuery, id); err != nil {
|
|
return nil, err
|
|
}
|
|
order.Items = items
|
|
return &order, nil
|
|
}
|
|
|
|
func (r *Repository) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
|
|
query := `UPDATE orders SET status = $1, updated_at = $2 WHERE id = $3`
|
|
res, err := r.db.ExecContext(ctx, query, status, time.Now().UTC(), id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows, err := res.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rows == 0 {
|
|
return errors.New("order not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// InitSchema applies a minimal schema for development environments.
|
|
func (r *Repository) InitSchema(ctx context.Context) error {
|
|
schema := `
|
|
CREATE TABLE IF NOT EXISTS companies (
|
|
id UUID PRIMARY KEY,
|
|
role TEXT NOT NULL,
|
|
cnpj TEXT NOT NULL UNIQUE,
|
|
corporate_name TEXT NOT NULL,
|
|
sanitary_license TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS products (
|
|
id UUID PRIMARY KEY,
|
|
seller_id UUID NOT NULL REFERENCES companies(id),
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
batch TEXT NOT NULL,
|
|
expires_at DATE NOT NULL,
|
|
price_cents BIGINT NOT NULL,
|
|
stock BIGINT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS orders (
|
|
id UUID PRIMARY KEY,
|
|
buyer_id UUID NOT NULL REFERENCES companies(id),
|
|
seller_id UUID NOT NULL REFERENCES companies(id),
|
|
status TEXT NOT NULL,
|
|
total_cents BIGINT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS order_items (
|
|
id UUID PRIMARY KEY,
|
|
order_id UUID NOT NULL REFERENCES orders(id),
|
|
product_id UUID NOT NULL REFERENCES products(id),
|
|
quantity BIGINT NOT NULL,
|
|
unit_cents BIGINT NOT NULL,
|
|
batch TEXT NOT NULL,
|
|
expires_at DATE NOT NULL
|
|
);
|
|
`
|
|
|
|
if _, err := r.db.ExecContext(ctx, schema); err != nil {
|
|
return fmt.Errorf("apply schema: %w", err)
|
|
}
|
|
return nil
|
|
}
|