rwadurian/backend/mpc-system/services/account/adapters/output/postgres/share_repo.go

285 lines
7.3 KiB
Go

package postgres
import (
"context"
"database/sql"
"errors"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/account/domain/entities"
"github.com/rwadurian/mpc-system/services/account/domain/repositories"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)
// AccountSharePostgresRepo implements AccountShareRepository using PostgreSQL
type AccountSharePostgresRepo struct {
db *sql.DB
}
// NewAccountSharePostgresRepo creates a new AccountSharePostgresRepo
func NewAccountSharePostgresRepo(db *sql.DB) repositories.AccountShareRepository {
return &AccountSharePostgresRepo{db: db}
}
// Create creates a new account share
func (r *AccountSharePostgresRepo) Create(ctx context.Context, share *entities.AccountShare) error {
query := `
INSERT INTO account_shares (id, account_id, share_type, party_id, party_index,
device_type, device_id, created_at, last_used_at, is_active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`
_, err := r.db.ExecContext(ctx, query,
share.ID,
share.AccountID.UUID(),
share.ShareType.String(),
share.PartyID,
share.PartyIndex,
share.DeviceType,
share.DeviceID,
share.CreatedAt,
share.LastUsedAt,
share.IsActive,
)
return err
}
// GetByID retrieves a share by ID
func (r *AccountSharePostgresRepo) GetByID(ctx context.Context, id string) (*entities.AccountShare, error) {
shareID, err := uuid.Parse(id)
if err != nil {
return nil, entities.ErrShareNotFound
}
query := `
SELECT id, account_id, share_type, party_id, party_index,
device_type, device_id, created_at, last_used_at, is_active
FROM account_shares
WHERE id = $1
`
return r.scanShare(r.db.QueryRowContext(ctx, query, shareID))
}
// GetByAccountID retrieves all shares for an account
func (r *AccountSharePostgresRepo) GetByAccountID(ctx context.Context, accountID value_objects.AccountID) ([]*entities.AccountShare, error) {
query := `
SELECT id, account_id, share_type, party_id, party_index,
device_type, device_id, created_at, last_used_at, is_active
FROM account_shares
WHERE account_id = $1
ORDER BY party_index
`
return r.queryShares(ctx, query, accountID.UUID())
}
// GetActiveByAccountID retrieves active shares for an account
func (r *AccountSharePostgresRepo) GetActiveByAccountID(ctx context.Context, accountID value_objects.AccountID) ([]*entities.AccountShare, error) {
query := `
SELECT id, account_id, share_type, party_id, party_index,
device_type, device_id, created_at, last_used_at, is_active
FROM account_shares
WHERE account_id = $1 AND is_active = TRUE
ORDER BY party_index
`
return r.queryShares(ctx, query, accountID.UUID())
}
// GetByPartyID retrieves shares by party ID
func (r *AccountSharePostgresRepo) GetByPartyID(ctx context.Context, partyID string) ([]*entities.AccountShare, error) {
query := `
SELECT id, account_id, share_type, party_id, party_index,
device_type, device_id, created_at, last_used_at, is_active
FROM account_shares
WHERE party_id = $1
ORDER BY created_at DESC
`
return r.queryShares(ctx, query, partyID)
}
// Update updates a share
func (r *AccountSharePostgresRepo) Update(ctx context.Context, share *entities.AccountShare) error {
query := `
UPDATE account_shares
SET share_type = $2, party_id = $3, party_index = $4,
device_type = $5, device_id = $6, last_used_at = $7, is_active = $8
WHERE id = $1
`
result, err := r.db.ExecContext(ctx, query,
share.ID,
share.ShareType.String(),
share.PartyID,
share.PartyIndex,
share.DeviceType,
share.DeviceID,
share.LastUsedAt,
share.IsActive,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return entities.ErrShareNotFound
}
return nil
}
// Delete deletes a share
func (r *AccountSharePostgresRepo) Delete(ctx context.Context, id string) error {
shareID, err := uuid.Parse(id)
if err != nil {
return entities.ErrShareNotFound
}
query := `DELETE FROM account_shares WHERE id = $1`
result, err := r.db.ExecContext(ctx, query, shareID)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return entities.ErrShareNotFound
}
return nil
}
// DeactivateByAccountID deactivates all shares for an account
func (r *AccountSharePostgresRepo) DeactivateByAccountID(ctx context.Context, accountID value_objects.AccountID) error {
query := `UPDATE account_shares SET is_active = FALSE WHERE account_id = $1`
_, err := r.db.ExecContext(ctx, query, accountID.UUID())
return err
}
// DeactivateByShareType deactivates shares of a specific type for an account
func (r *AccountSharePostgresRepo) DeactivateByShareType(ctx context.Context, accountID value_objects.AccountID, shareType value_objects.ShareType) error {
query := `UPDATE account_shares SET is_active = FALSE WHERE account_id = $1 AND share_type = $2`
_, err := r.db.ExecContext(ctx, query, accountID.UUID(), shareType.String())
return err
}
// scanShare scans a single share row
func (r *AccountSharePostgresRepo) scanShare(row *sql.Row) (*entities.AccountShare, error) {
var (
id uuid.UUID
accountID uuid.UUID
shareType string
partyID string
partyIndex int
deviceType sql.NullString
deviceID sql.NullString
share entities.AccountShare
)
err := row.Scan(
&id,
&accountID,
&shareType,
&partyID,
&partyIndex,
&deviceType,
&deviceID,
&share.CreatedAt,
&share.LastUsedAt,
&share.IsActive,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, entities.ErrShareNotFound
}
return nil, err
}
share.ID = id
share.AccountID = value_objects.AccountIDFromUUID(accountID)
share.ShareType = value_objects.ShareType(shareType)
share.PartyID = partyID
share.PartyIndex = partyIndex
if deviceType.Valid {
share.DeviceType = &deviceType.String
}
if deviceID.Valid {
share.DeviceID = &deviceID.String
}
return &share, nil
}
// queryShares queries multiple shares
func (r *AccountSharePostgresRepo) queryShares(ctx context.Context, query string, args ...interface{}) ([]*entities.AccountShare, error) {
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var shares []*entities.AccountShare
for rows.Next() {
var (
id uuid.UUID
accountID uuid.UUID
shareType string
partyID string
partyIndex int
deviceType sql.NullString
deviceID sql.NullString
share entities.AccountShare
)
err := rows.Scan(
&id,
&accountID,
&shareType,
&partyID,
&partyIndex,
&deviceType,
&deviceID,
&share.CreatedAt,
&share.LastUsedAt,
&share.IsActive,
)
if err != nil {
return nil, err
}
share.ID = id
share.AccountID = value_objects.AccountIDFromUUID(accountID)
share.ShareType = value_objects.ShareType(shareType)
share.PartyID = partyID
share.PartyIndex = partyIndex
if deviceType.Valid {
share.DeviceType = &deviceType.String
}
if deviceID.Valid {
share.DeviceID = &deviceID.String
}
shares = append(shares, &share)
}
return shares, rows.Err()
}