Skip to content

Commit 1480bec

Browse files
committed
feat(contact): implement info command and add integration test
1 parent dc8561a commit 1480bec

File tree

12 files changed

+908
-32
lines changed

12 files changed

+908
-32
lines changed

epp/server/create.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ func handleCreate(
2828
case data.Domain != nil:
2929
return createDomain(ctx, connection, e, *data.Domain)
3030
case data.Contact != nil:
31-
resp = e.ContactService.Create(ctx, *data.Contact, connection.UserId())
31+
resp = e.ContactService.Create(
32+
ctx,
33+
*data.Contact,
34+
connection.UserId(),
35+
connection.Username(),
36+
)
3237
default:
3338
resp = response.AnyError(2101, response.UnimplementedCommand)
3439
}

epp/server/info.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package server
22

33
import (
44
"context"
5+
"fmt"
56

67
"github.com/pixel365/goepp/command"
8+
"github.com/pixel365/goepp/command/info"
9+
"github.com/pixel365/goepp/response"
710

811
"github.com/pixel365/zoner/epp/server/conn"
912
)
@@ -14,5 +17,19 @@ func handleInfo(
1417
cmd command.Commander,
1518
e *Epp,
1619
) error {
17-
return e.DomainService.Info(ctx)
20+
var resp response.Marshaller
21+
data, _ := cmd.(*info.Info)
22+
23+
switch {
24+
case data.Domain != nil:
25+
return e.DomainService.Info(ctx)
26+
case data.Contact != nil:
27+
resp = e.ContactService.Info(ctx, *data.Contact, connection.UserId(), connection.Username())
28+
}
29+
30+
if err := connection.Write(ctx, resp, e.Metrics.IncBytes); err != nil {
31+
return fmt.Errorf("write response error: %w", err)
32+
}
33+
34+
return nil
1835
}

internal/model/contact.go

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package model
22

3+
import "time"
4+
35
type ContactPostalFields struct {
46
Typ string `json:"-"`
57
PostalName string `json:"-"`
@@ -17,16 +19,17 @@ type Disclose struct {
1719
}
1820

1921
type ContactCreateInput struct {
20-
Disclose *Disclose `json:",inline"`
21-
ContactID string
22-
Name string
23-
Organization string
24-
Email string
25-
Voice string
26-
Fax string
27-
AuthInfoHash string
28-
PostalInfo []ContactPostalFields
29-
RegistrarID int64
22+
Disclose *Disclose `json:",inline"`
23+
ContactID string
24+
Name string
25+
Organization string
26+
Email string
27+
Voice string
28+
Fax string
29+
AuthInfoHash string
30+
RegistrarName string
31+
PostalInfo []ContactPostalFields
32+
RegistrarID int64
3033
}
3134

3235
type ContactsIdentifiersInput struct {
@@ -38,3 +41,49 @@ type CheckedContact struct {
3841
ID string
3942
Available bool
4043
}
44+
45+
type ContactInfoInput struct {
46+
ContactID string
47+
Password string
48+
RegistrarID int64
49+
}
50+
51+
type ContactInfoStatus struct {
52+
CreatedAt time.Time `json:"created_at"`
53+
Reason *string `json:"reason"`
54+
CreatedByClient *string `json:"created_by_client"`
55+
Status string `json:"status"`
56+
Source string `json:"source"`
57+
}
58+
59+
type ContactInfoPostalInfo struct {
60+
Name *string `json:"name"`
61+
PostalName *string `json:"postal_name"`
62+
PostalOrg *string `json:"postal_org"`
63+
PostalCode *string `json:"postal_code"`
64+
City *string `json:"city"`
65+
CountryCode *string `json:"country_code"`
66+
StateProvince *string `json:"state_province"`
67+
Type string `json:"type"`
68+
Streets []string `json:"streets"`
69+
}
70+
71+
type ContactInfo struct {
72+
CreatedAt time.Time
73+
UpdatedAt time.Time
74+
Fax *string
75+
Disclose *Disclose
76+
UpdatedByClientID *string
77+
CreatedByClientID *string
78+
Organization *string
79+
Voice *string
80+
AuthInfoHash string
81+
Roid string
82+
ContactID string
83+
Email string
84+
Name string
85+
Statuses []ContactInfoStatus
86+
PostalInfo []ContactInfoPostalInfo
87+
RegistrarID int64
88+
ID int64
89+
}

internal/repository/contact.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ import (
99
type ContactRepository interface {
1010
Create(context.Context, model.ContactCreateInput) (int64, error)
1111
Check(context.Context, model.ContactsIdentifiersInput) ([]model.CheckedContact, error)
12+
Info(context.Context, model.ContactInfoInput) (model.ContactInfo, error)
1213
}

internal/repository/contact/create.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ INSERT INTO contacts (
4040
voice,
4141
fax,
4242
auth_info_hash,
43-
disclose
44-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
43+
disclose,
44+
created_by_client_id,
45+
updated_by_client_id
46+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
4547
RETURNING id
4648
`
4749
var disclose any = struct{}{}
@@ -52,9 +54,21 @@ RETURNING id
5254
b, _ := json.Marshal(disclose)
5355

5456
return func(tx pgx.Tx) error {
55-
err := tx.QueryRow(ctx, sql,
56-
data.ContactID, data.RegistrarID, data.Name, data.Email, data.Organization,
57-
data.Voice, data.Fax, data.AuthInfoHash, b).Scan(contactId)
57+
err := tx.QueryRow(
58+
ctx,
59+
sql,
60+
data.ContactID,
61+
data.RegistrarID,
62+
data.Name,
63+
data.Email,
64+
data.Organization,
65+
data.Voice,
66+
data.Fax,
67+
data.AuthInfoHash,
68+
b,
69+
data.RegistrarName,
70+
data.RegistrarName,
71+
).Scan(contactId)
5872

5973
if err == nil {
6074
return nil

internal/repository/contact/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "errors"
44

55
var (
66
ErrAlreadyExists = errors.New("contact already exists")
7+
ErrNotFound = errors.New("contact not found")
78
ErrValidation = errors.New("validation error")
89
ErrInternal = errors.New("internal error")
910
)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package contact
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/jackc/pgx/v5"
10+
11+
"github.com/pixel365/zoner/internal/model"
12+
)
13+
14+
func (r *Repository) Info(
15+
ctx context.Context,
16+
data model.ContactInfoInput,
17+
) (model.ContactInfo, error) {
18+
var result model.ContactInfo
19+
var discloseRaw []byte
20+
var statusesRaw []byte
21+
var postalInfoRaw []byte
22+
23+
sql := `
24+
SELECT
25+
c.id,
26+
c.contact_id,
27+
c.roid,
28+
c.registrar_id,
29+
c.name,
30+
c.email,
31+
c.organization,
32+
c.voice,
33+
c.fax,
34+
c.auth_info_hash,
35+
c.disclose,
36+
c.created_at,
37+
c.updated_at,
38+
c.created_by_client_id,
39+
c.updated_by_client_id,
40+
41+
COALESCE((
42+
SELECT jsonb_agg(
43+
jsonb_build_object(
44+
'status', cs.status,
45+
'source', cs.source,
46+
'reason', cs.reason,
47+
'created_at', cs.created_at,
48+
'created_by_client', cs.created_by_client
49+
)
50+
ORDER BY cs.id
51+
)
52+
FROM contact_statuses cs
53+
WHERE cs.contact_id = c.id
54+
), '[]'::jsonb) AS statuses,
55+
56+
COALESCE((
57+
SELECT jsonb_agg(
58+
jsonb_build_object(
59+
'type', pi.type,
60+
'name', pi.name,
61+
'postal_name', pi.postal_name,
62+
'postal_org', pi.postal_org,
63+
'postal_code', pi.postal_code,
64+
'city', pi.city,
65+
'country_code', pi.country_code,
66+
'streets', pi.streets,
67+
'state_province', pi.state_province
68+
)
69+
ORDER BY pi.id
70+
)
71+
FROM contacts_postal_info pi
72+
WHERE pi.contact_id = c.id
73+
), '[]'::jsonb) AS postal_info
74+
75+
FROM contacts c
76+
WHERE c.contact_id = $1
77+
AND c.registrar_id = $2
78+
AND c.deleted_at IS NULL
79+
LIMIT 1
80+
`
81+
82+
err := r.db.QueryRow(ctx, sql, data.ContactID, data.RegistrarID).Scan(
83+
&result.ID,
84+
&result.ContactID,
85+
&result.Roid,
86+
&result.RegistrarID,
87+
&result.Name,
88+
&result.Email,
89+
&result.Organization,
90+
&result.Voice,
91+
&result.Fax,
92+
&result.AuthInfoHash,
93+
&discloseRaw,
94+
&result.CreatedAt,
95+
&result.UpdatedAt,
96+
&result.CreatedByClientID,
97+
&result.UpdatedByClientID,
98+
&statusesRaw,
99+
&postalInfoRaw,
100+
)
101+
if err != nil {
102+
switch {
103+
case errors.Is(err, pgx.ErrNoRows):
104+
return model.ContactInfo{}, fmt.Errorf(
105+
"%w, contact with id %s not found",
106+
ErrNotFound,
107+
data.ContactID,
108+
)
109+
default:
110+
return model.ContactInfo{}, fmt.Errorf("%w, %w", ErrInternal, err)
111+
}
112+
}
113+
114+
if len(discloseRaw) > 0 && string(discloseRaw) != "{}" {
115+
result.Disclose = &model.Disclose{}
116+
if err = json.Unmarshal(discloseRaw, result.Disclose); err != nil {
117+
return model.ContactInfo{}, fmt.Errorf("%w, unmarshal disclose: %w", ErrInternal, err)
118+
}
119+
}
120+
121+
if len(statusesRaw) > 0 {
122+
if err = json.Unmarshal(statusesRaw, &result.Statuses); err != nil {
123+
return model.ContactInfo{}, fmt.Errorf("%w, unmarshal statuses: %w", ErrInternal, err)
124+
}
125+
}
126+
127+
if len(postalInfoRaw) > 0 {
128+
if err = json.Unmarshal(postalInfoRaw, &result.PostalInfo); err != nil {
129+
return model.ContactInfo{}, fmt.Errorf(
130+
"%w, unmarshal postal info: %w",
131+
ErrInternal,
132+
err,
133+
)
134+
}
135+
}
136+
137+
return result, nil
138+
}

internal/service/contact/create.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func (s *Service) Create(
1818
ctx context.Context,
1919
payload create.Contact,
2020
registrarId int64,
21+
registrarName string,
2122
) response.Marshaller {
2223
passwordHash, _ := password.Hash(payload.AuthInfo.Password, password.DefaultParams)
2324

@@ -33,15 +34,16 @@ func (s *Service) Create(
3334
}
3435

3536
contact := model.ContactCreateInput{
36-
ContactID: payload.ID,
37-
Name: "",
38-
Organization: "",
39-
Email: payload.Email,
40-
Voice: voice,
41-
Fax: fax,
42-
AuthInfoHash: passwordHash,
43-
Disclose: nil,
44-
RegistrarID: registrarId,
37+
ContactID: payload.ID,
38+
Name: "",
39+
Organization: "",
40+
Email: payload.Email,
41+
Voice: voice,
42+
Fax: fax,
43+
AuthInfoHash: passwordHash,
44+
Disclose: nil,
45+
RegistrarID: registrarId,
46+
RegistrarName: registrarName,
4547
}
4648

4749
if len(payload.PostalInfo) > 0 {
@@ -70,7 +72,11 @@ func (s *Service) Create(
7072
}
7173
di := map[string]struct{}{}
7274
for i := range payload.Disclose.Items {
73-
di[payload.Disclose.Items[i].Name] = struct{}{}
75+
field := payload.Disclose.Items[i].Name
76+
if field == DiscloseAddr.String() && payload.Disclose.Items[i].Type != "" {
77+
field += ":" + string(payload.Disclose.Items[i].Type)
78+
}
79+
di[field] = struct{}{}
7480
}
7581
for k := range di {
7682
disclose.Fields = append(disclose.Fields, k)

0 commit comments

Comments
 (0)