Skip to content

Commit dc8561a

Browse files
committed
feat(contact): implement check command with integration tests
1 parent 4f060cc commit dc8561a

File tree

9 files changed

+481
-7
lines changed

9 files changed

+481
-7
lines changed

epp/server/check.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/check"
9+
"github.com/pixel365/goepp/response"
710

811
"github.com/pixel365/zoner/epp/server/conn"
912
)
@@ -14,5 +17,19 @@ func handleCheck(
1417
cmd command.Commander,
1518
e *Epp,
1619
) error {
17-
return e.DomainService.Check(ctx)
20+
var resp response.Marshaller
21+
data, _ := cmd.(*check.Check)
22+
23+
switch {
24+
case data.Domain != nil:
25+
return e.DomainService.Check(ctx)
26+
case data.Contact != nil:
27+
resp = e.ContactService.Check(ctx, *data.Contact, connection.UserId())
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ type ContactCreateInput struct {
2828
PostalInfo []ContactPostalFields
2929
RegistrarID int64
3030
}
31+
32+
type ContactsIdentifiersInput struct {
33+
Identifiers []string
34+
RegistrarID int64
35+
}
36+
37+
type CheckedContact struct {
38+
ID string
39+
Available bool
40+
}

internal/repository/contact.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ import (
88

99
type ContactRepository interface {
1010
Create(context.Context, model.ContactCreateInput) (int64, error)
11+
Check(context.Context, model.ContactsIdentifiersInput) ([]model.CheckedContact, error)
1112
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package contact
2+
3+
import (
4+
"context"
5+
6+
"github.com/pixel365/zoner/internal/model"
7+
)
8+
9+
func (r *Repository) Check(
10+
ctx context.Context,
11+
data model.ContactsIdentifiersInput,
12+
) ([]model.CheckedContact, error) {
13+
var result []model.CheckedContact
14+
15+
sql := `
16+
WITH input AS (
17+
SELECT
18+
v.contact_id AS requested_id,
19+
lower(v.contact_id) AS normalized_id,
20+
v.ord
21+
FROM unnest($1::text[]) WITH ORDINALITY AS v(contact_id, ord)
22+
)
23+
SELECT
24+
input.requested_id,
25+
c.id IS NULL AS available
26+
FROM input
27+
LEFT JOIN contacts c
28+
ON lower(c.contact_id) = input.normalized_id
29+
AND c.registrar_id = $2
30+
AND c.deleted_at IS NULL
31+
ORDER BY input.ord
32+
`
33+
34+
rows, err := r.db.Query(ctx, sql, data.Identifiers, data.RegistrarID)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
defer rows.Close()
40+
41+
for rows.Next() {
42+
contact := model.CheckedContact{
43+
ID: "",
44+
Available: false,
45+
}
46+
47+
err = rows.Scan(&contact.ID, &contact.Available)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
result = append(result, contact)
53+
}
54+
55+
if rows.Err() != nil {
56+
return nil, rows.Err()
57+
}
58+
59+
return result, nil
60+
}

internal/service/contact/check.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,59 @@ package contact
22

33
import (
44
"context"
5-
"errors"
5+
6+
"github.com/pixel365/goepp/command/check"
7+
"github.com/pixel365/goepp/response"
8+
9+
"github.com/pixel365/zoner/internal/model"
610
)
711

8-
func (s *Service) Check(ctx context.Context) error {
9-
return errors.New("not implemented")
12+
func (s *Service) Check(
13+
ctx context.Context,
14+
payload check.ContactCheck,
15+
registrarId int64,
16+
) response.Marshaller {
17+
//TODO: check current limits on the number of contacts requested
18+
19+
identifiers := model.ContactsIdentifiersInput{
20+
Identifiers: payload.IDs,
21+
RegistrarID: registrarId,
22+
}
23+
24+
var resp response.Marshaller
25+
code := response.CodeCommandCompletedSuccessfully
26+
27+
checkedContacts, err := s.repo.Check(ctx, identifiers)
28+
if err != nil {
29+
s.log.WithUserId(registrarId).Error("contacts check error", err)
30+
code = response.CodeCommandFailed
31+
return response.AnyError(code, code.String())
32+
}
33+
34+
inUse := "In Use"
35+
data := ContactsCheckResData{}
36+
for _, contact := range checkedContacts {
37+
var reason *string
38+
var available uint8
39+
if contact.Available {
40+
available = 1
41+
}
42+
43+
if available == 0 {
44+
reason = &inUse
45+
}
46+
47+
data.Contacts = append(data.Contacts, SingleCheckContact{
48+
ID: CheckContactID{
49+
Available: available,
50+
Value: contact.ID,
51+
},
52+
Reason: reason,
53+
})
54+
}
55+
56+
resp = response.NewResponse[ContactsCheckResData, struct{}](code, code.String()).
57+
WithResData(data)
58+
59+
return resp
1060
}

internal/service/contact/create.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,24 @@ func (s *Service) Create(
2121
) response.Marshaller {
2222
passwordHash, _ := password.Hash(payload.AuthInfo.Password, password.DefaultParams)
2323

24+
var voice string
25+
var fax string
26+
27+
if payload.Voice != nil {
28+
voice = payload.Voice.Num
29+
}
30+
31+
if payload.Fax != nil {
32+
fax = payload.Fax.Num
33+
}
34+
2435
contact := model.ContactCreateInput{
2536
ContactID: payload.ID,
2637
Name: "",
2738
Organization: "",
2839
Email: payload.Email,
29-
Voice: payload.Voice.Num,
30-
Fax: payload.Fax.Num,
40+
Voice: voice,
41+
Fax: fax,
3142
AuthInfoHash: passwordHash,
3243
Disclose: nil,
3344
RegistrarID: registrarId,

internal/service/contact/model.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,95 @@ type ContactCreateResData struct {
77
ID string `xml:"urn:ietf:params:xml:ns:contact-1.0 id"`
88
CRDate string `xml:"urn:ietf:params:xml:ns:contact-1.0 crDate"`
99
}
10+
11+
type CheckContactID struct {
12+
Value string `xml:",chardata"`
13+
Available uint8 `xml:"avail,attr"`
14+
}
15+
16+
type SingleCheckContact struct {
17+
Reason *string `xml:"reason,omitempty"`
18+
ID CheckContactID `xml:"urn:ietf:params:xml:ns:contact-1.0 id"`
19+
}
20+
21+
type ContactsCheckResData struct {
22+
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:contact-1.0 chkData"`
23+
Contacts []SingleCheckContact `xml:"urn:ietf:params:xml:ns:contact-1.0 cd"`
24+
}
25+
26+
func (d ContactsCheckResData) MarshalXML(enc *xml.Encoder, _ xml.StartElement) error {
27+
start := xml.StartElement{
28+
Name: xml.Name{Local: "contact:chkData"},
29+
Attr: []xml.Attr{
30+
{
31+
Name: xml.Name{Local: "xmlns:contact"},
32+
Value: "urn:ietf:params:xml:ns:contact-1.0",
33+
},
34+
},
35+
}
36+
37+
if err := enc.EncodeToken(start); err != nil {
38+
return err
39+
}
40+
41+
for _, contact := range d.Contacts {
42+
if err := enc.Encode(contact); err != nil {
43+
return err
44+
}
45+
}
46+
47+
if err := enc.EncodeToken(start.End()); err != nil {
48+
return err
49+
}
50+
51+
return enc.Flush()
52+
}
53+
54+
func (c SingleCheckContact) MarshalXML(enc *xml.Encoder, _ xml.StartElement) error {
55+
start := xml.StartElement{Name: xml.Name{Local: "contact:cd"}}
56+
if err := enc.EncodeToken(start); err != nil {
57+
return err
58+
}
59+
60+
if err := enc.Encode(c.ID); err != nil {
61+
return err
62+
}
63+
64+
if c.Reason != nil {
65+
if err := enc.EncodeElement(*c.Reason, xml.StartElement{Name: xml.Name{Local: "contact:reason"}}); err != nil {
66+
return err
67+
}
68+
}
69+
70+
if err := enc.EncodeToken(start.End()); err != nil {
71+
return err
72+
}
73+
74+
return enc.Flush()
75+
}
76+
77+
func (i CheckContactID) MarshalXML(enc *xml.Encoder, _ xml.StartElement) error {
78+
start := xml.StartElement{
79+
Name: xml.Name{Local: "contact:id"},
80+
Attr: []xml.Attr{
81+
{
82+
Name: xml.Name{Local: "avail"},
83+
Value: string('0' + rune(i.Available)),
84+
},
85+
},
86+
}
87+
88+
if err := enc.EncodeToken(start); err != nil {
89+
return err
90+
}
91+
92+
if err := enc.EncodeToken(xml.CharData(i.Value)); err != nil {
93+
return err
94+
}
95+
96+
if err := enc.EncodeToken(start.End()); err != nil {
97+
return err
98+
}
99+
100+
return enc.Flush()
101+
}

internal/service/contact/service.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package contact
33
import (
44
"context"
55

6+
"github.com/pixel365/goepp/command/check"
67
"github.com/pixel365/goepp/command/create"
78
"github.com/pixel365/goepp/response"
89

@@ -15,7 +16,7 @@ var _ ContactService = (*Service)(nil)
1516

1617
type ContactService interface {
1718
Info(context.Context) error
18-
Check(context.Context) error
19+
Check(context.Context, check.ContactCheck, int64) response.Marshaller
1920
Create(context.Context, create.Contact, int64) response.Marshaller
2021
}
2122

0 commit comments

Comments
 (0)