mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-25 22:22:16 +00:00
- Add profileRow struct with ICCID/EID/IMSI/MCC/MNC/ProfileType/State/TenantID/ActivationCode/CreatedAt/UpdatedAt fields mapped to esim_profiles table with indexes on EID/IMSI/State/TenantID - Add NewPostgresProfileStore with DSN validation, GORM connection setup with Warn logging, and AutoMigrate for esim_profiles schema - Add Create/Get/List/UpdateState/Delete methods implementing ProfileRepository interface with
181 lines
4.7 KiB
Go
181 lines
4.7 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
// profileRow is the GORM-mapped row for the esim_profiles table.
|
|
type profileRow struct {
|
|
ICCID string `gorm:"primaryKey;size:32"`
|
|
EID string `gorm:"size:64;index"`
|
|
IMSI string `gorm:"size:20;index"`
|
|
MCC string `gorm:"size:4"`
|
|
MNC string `gorm:"size:4"`
|
|
ProfileType string `gorm:"size:32"`
|
|
State string `gorm:"size:32;index"`
|
|
TenantID string `gorm:"size:64;index"`
|
|
ActivationCode string `gorm:"size:255"`
|
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
|
}
|
|
|
|
// TableName sets the Postgres table name.
|
|
func (profileRow) TableName() string { return "esim_profiles" }
|
|
|
|
// PostgresProfileStore is a Postgres-backed ProfileRepository.
|
|
type PostgresProfileStore struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewPostgresProfileStore connects to Postgres using the given DSN, runs the
|
|
// profile schema migration, and returns a repository ready to use.
|
|
func NewPostgresProfileStore(dsn string) (*PostgresProfileStore, error) {
|
|
if dsn == "" {
|
|
return nil, errors.New("postgres DSN is empty")
|
|
}
|
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Warn),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open postgres: %w", err)
|
|
}
|
|
if err := db.AutoMigrate(&profileRow{}); err != nil {
|
|
return nil, fmt.Errorf("migrate esim_profiles: %w", err)
|
|
}
|
|
return &PostgresProfileStore{db: db}, nil
|
|
}
|
|
|
|
// Create inserts a profile row.
|
|
func (s *PostgresProfileStore) Create(ctx context.Context, p *Profile) error {
|
|
row := toRow(p)
|
|
if err := s.db.WithContext(ctx).Create(row).Error; err != nil {
|
|
return err
|
|
}
|
|
*p = *fromRow(row)
|
|
return nil
|
|
}
|
|
|
|
// Get fetches a profile by ICCID.
|
|
func (s *PostgresProfileStore) Get(ctx context.Context, iccid string) (*Profile, error) {
|
|
var row profileRow
|
|
err := s.db.WithContext(ctx).First(&row, "iccid = ?", iccid).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fromRow(&row), nil
|
|
}
|
|
|
|
// List returns a filtered, paginated page of profiles.
|
|
func (s *PostgresProfileStore) List(ctx context.Context, f ListFilter) ([]*Profile, int, error) {
|
|
q := s.db.WithContext(ctx).Model(&profileRow{})
|
|
if f.TenantID != "" {
|
|
q = q.Where("tenant_id = ?", f.TenantID)
|
|
}
|
|
if f.State != "" {
|
|
q = q.Where("state = ?", f.State)
|
|
}
|
|
|
|
var total int64
|
|
if err := q.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
if f.Limit > 0 {
|
|
q = q.Limit(f.Limit)
|
|
}
|
|
if f.Offset > 0 {
|
|
q = q.Offset(f.Offset)
|
|
}
|
|
|
|
var rows []profileRow
|
|
if err := q.Order("created_at DESC").Find(&rows).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
result := make([]*Profile, len(rows))
|
|
for i := range rows {
|
|
result[i] = fromRow(&rows[i])
|
|
}
|
|
return result, int(total), nil
|
|
}
|
|
|
|
// UpdateState updates the state column and returns the refreshed profile.
|
|
func (s *PostgresProfileStore) UpdateState(ctx context.Context, iccid, state string) (*Profile, error) {
|
|
res := s.db.WithContext(ctx).Model(&profileRow{}).
|
|
Where("iccid = ?", iccid).
|
|
Update("state", state)
|
|
if res.Error != nil {
|
|
return nil, res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return nil, ErrNotFound
|
|
}
|
|
return s.Get(ctx, iccid)
|
|
}
|
|
|
|
// Delete removes a profile row.
|
|
func (s *PostgresProfileStore) Delete(ctx context.Context, iccid string) error {
|
|
res := s.db.WithContext(ctx).Delete(&profileRow{}, "iccid = ?", iccid)
|
|
if res.Error != nil {
|
|
return res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close releases the underlying database connection.
|
|
func (s *PostgresProfileStore) Close() error {
|
|
sqlDB, err := s.db.DB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sqlDB.Close()
|
|
}
|
|
|
|
// toRow maps domain -> row. Passthrough of CreatedAt/UpdatedAt lets GORM hooks
|
|
// populate them on first save.
|
|
func toRow(p *Profile) *profileRow {
|
|
return &profileRow{
|
|
ICCID: p.ICCID,
|
|
EID: p.EID,
|
|
IMSI: p.IMSI,
|
|
MCC: p.MCC,
|
|
MNC: p.MNC,
|
|
ProfileType: p.ProfileType,
|
|
State: p.State,
|
|
TenantID: p.TenantID,
|
|
ActivationCode: p.ActivationCode,
|
|
CreatedAt: p.CreatedAt,
|
|
UpdatedAt: p.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
// fromRow maps row -> domain.
|
|
func fromRow(r *profileRow) *Profile {
|
|
return &Profile{
|
|
ICCID: r.ICCID,
|
|
EID: r.EID,
|
|
IMSI: r.IMSI,
|
|
MCC: r.MCC,
|
|
MNC: r.MNC,
|
|
ProfileType: r.ProfileType,
|
|
State: r.State,
|
|
TenantID: r.TenantID,
|
|
ActivationCode: r.ActivationCode,
|
|
CreatedAt: r.CreatedAt,
|
|
UpdatedAt: r.UpdatedAt,
|
|
}
|
|
}
|