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 }