package vm import ( "context" "database/sql" "errors" ) type SQLRepository struct { db *sql.DB } func NewSQLRepository(db *sql.DB) SQLRepository { return SQLRepository{db: db} } func (r SQLRepository) ListProjectVMs(ctx context.Context, profileID string, projectID string) ([]VM, bool, error) { rows, err := r.db.QueryContext(ctx, ` select v.id::text, v.project_id::text, p.tenant_id::text, v.cluster_id::text, v.proxmox_vmid, v.node, v.name, v.status::text, v.vcpu, v.ram_mb, v.disk_gb, v.created_at, v.updated_at, p.name, m.role::text from public.projects p join public.memberships m on m.tenant_id = p.tenant_id and m.profile_id = $1 left join public.vms v on v.project_id = p.id where p.id = $2 order by v.created_at desc nulls last, v.name asc `, profileID, projectID) if err != nil { return nil, false, err } defer rows.Close() var vms []VM foundProject := false for rows.Next() { var record nullableVMRecord if err := rows.Scan( &record.ID, &record.ProjectID, &record.TenantID, &record.ClusterID, &record.ProxmoxVMID, &record.Node, &record.Name, &record.Status, &record.VCPU, &record.RAMMB, &record.DiskGB, &record.CreatedAt, &record.UpdatedAt, &record.ProjectName, &record.MembershipRole, ); err != nil { return nil, false, err } foundProject = true if !record.ID.Valid { continue } vms = append(vms, record.vm()) } if err := rows.Err(); err != nil { return nil, false, err } return vms, foundProject, nil } func (r SQLRepository) GetVM(ctx context.Context, profileID string, vmID string) (VM, bool, error) { var vm VM err := r.db.QueryRowContext(ctx, ` select v.id::text, v.project_id::text, p.tenant_id::text, v.cluster_id::text, v.proxmox_vmid, v.node, v.name, v.status::text, v.vcpu, v.ram_mb, v.disk_gb, v.created_at, v.updated_at, p.name, m.role::text from public.vms v join public.projects p on p.id = v.project_id join public.memberships m on m.tenant_id = p.tenant_id and m.profile_id = $1 where v.id = $2 `, profileID, vmID).Scan( &vm.ID, &vm.ProjectID, &vm.TenantID, &vm.ClusterID, &vm.ProxmoxVMID, &vm.Node, &vm.Name, &vm.Status, &vm.VCPU, &vm.RAMMB, &vm.DiskGB, &vm.CreatedAt, &vm.UpdatedAt, &vm.ProjectName, &vm.MembershipRole, ) if errors.Is(err, sql.ErrNoRows) { return VM{}, false, nil } if err != nil { return VM{}, false, err } return vm, true, nil } type nullableVMRecord struct { ID sql.NullString ProjectID sql.NullString TenantID sql.NullString ClusterID sql.NullString ProxmoxVMID sql.NullInt64 Node sql.NullString Name sql.NullString Status sql.NullString VCPU sql.NullInt64 RAMMB sql.NullInt64 DiskGB sql.NullInt64 CreatedAt sql.NullTime UpdatedAt sql.NullTime ProjectName string MembershipRole string } func (r nullableVMRecord) vm() VM { return VM{ ID: r.ID.String, ProjectID: r.ProjectID.String, TenantID: r.TenantID.String, ClusterID: r.ClusterID.String, ProxmoxVMID: int(r.ProxmoxVMID.Int64), Node: r.Node.String, Name: r.Name.String, Status: r.Status.String, VCPU: int(r.VCPU.Int64), RAMMB: int(r.RAMMB.Int64), DiskGB: int(r.DiskGB.Int64), CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time, ProjectName: r.ProjectName, MembershipRole: r.MembershipRole, } }