package outbox import ( "context" "database/sql" "encoding/json" "errors" "time" ) var ( ErrSerialize = errors.New("error serialize/deserialize") ErrInsert = errors.New("error create") ErrNotFound = errors.New("not found") ErrRead = errors.New("error read") ErrUpdate = errors.New("error update") ErrDelete = errors.New("error delete") ) func OpenOrCreate[T any](ctx context.Context, db *sql.DB, tablename string) (*Repo[T], error) { if err := initDB(ctx, db, tablename); err != nil { return nil, err } return &Repo[T]{ db: db, table: tablename, }, nil } type Repo[T any] struct { db *sql.DB table string } func (r *Repo[T]) CreateOrUpdate(ctx context.Context, id string, v *T) error { now := time.Now() stmt := "INSERT INTO $1 (id, created_at, updated_at, payload) VALUES ($2, $3, $4)" b, err := serialize(v) if err != nil { return err } if _, err := r.db.ExecContext(ctx, stmt, r.table, id, now, now, b); err != nil { return errors.Join(ErrInsert, err) } return nil } func (r *Repo[T]) Read(ctx context.Context, id string) (*T, error) { stmt := "SELECT payload FROM $1 WHERE id = $2 AND deleted_at is NULL" row, err := r.db.QueryContext(ctx, stmt, r.table, id) if err != nil { // TODO err not found return nil, errors.Join(ErrRead, err) } b := []byte{} if err := row.Scan(&b); err != nil { return nil, err } v, err := deserialize[T](b) if err != nil { return nil, err } return v, nil } func (r *Repo[T]) Update(ctx context.Context, id string, v *T) error { now := time.Now() stmt := "UPDATE $1 SET updated_at = $3, payload = $4 WHERE id = $2" b, err := json.Marshal(v) if err != nil { return errors.Join(ErrSerialize, err) } if _, err := r.db.ExecContext(ctx, stmt, r.table, id, now, b); err != nil { return errors.Join(ErrUpdate, err) } return nil } func (r *Repo[T]) Delete(ctx context.Context, id string) error { now := time.Now() stmt := "UPDATE $1 SET deleted_at = $3 WHERE id = $2" if _, err := r.db.ExecContext(ctx, stmt, r.table, now, id); err != nil { return errors.Join(ErrDelete, err) } return nil } func serialize(v any) ([]byte, error) { b, err := json.Marshal(v) if err != nil { return nil, errors.Join(ErrSerialize, err) } return b, nil } func deserialize[T any](b []byte) (*T, error) { v := new(T) if err := json.Unmarshal(b, v); err != nil { return nil, errors.Join(ErrSerialize, err) } return v, nil }