Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get records API route #178

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/qdm12/ddns-updater/internal/records"
)

//go:generate mockgen -destination=mock_$GOPACKAGE/$GOFILE . Database

type Database interface {
Close() error
Select(id int) (record records.Record, err error)
Expand Down
5 changes: 4 additions & 1 deletion internal/data/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ func (db *database) Select(id int) (record records.Record, err error) {
return db.data[id], nil
}

func (db *database) SelectAll() (records []records.Record) {
func (db *database) SelectAll() []records.Record {
db.RLock()
defer db.RUnlock()
if db.data == nil {
db.data = make([]records.Record, 0)
}
return db.data
}
108 changes: 108 additions & 0 deletions internal/data/mock_data/data.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions internal/server/getrecords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package server

import (
"encoding/json"
"net/http"
)

func (h *handlers) getRecords(w http.ResponseWriter, r *http.Request) {
accept := r.Header.Get("Accept")
var contentType string
switch accept {
case "", "application/json":
contentType = "application/json"
default:
httpError(w, http.StatusBadRequest, `content type "`+accept+`" is not supported`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should you not return here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is just sending an http response as json {"error": "message"} with the status code given 😉
I'll get to authentication soon to merge that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point 🤣. I reviewed the rest, all seem alright to me. I learnt about Read Locks, it seemed weird to me to have to lock in order to read. Regarding authentication, a cool thing could be to make ddns-updater a microservice that implements oauth2. We could interface it with the user management interface we want, we wouldn't have to maintain user management. I'm personally using Authelia which has oauth2 in beta. There is also https://github.com/netlify/gotrue

}
w.Header().Set("Content-Type", contentType)

records := h.db.SelectAll()
encoder := json.NewEncoder(w)
// TODO check Accept header and return Content-Type header
if err := encoder.Encode(records); err != nil {
h.logger.Error(err)
httpError(w, http.StatusInternalServerError, "")
}
}
90 changes: 90 additions & 0 deletions internal/server/getrecords_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package server

import (
"net"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/qdm12/ddns-updater/internal/constants"
"github.com/qdm12/ddns-updater/internal/data/mock_data"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/records"
"github.com/qdm12/ddns-updater/internal/settings/mock_settings"
"github.com/stretchr/testify/assert"
)

func Test_handlers_getRecords(t *testing.T) {
t.Parallel()

exampleTime := time.Unix(1000, 0)

exampleRecord := records.Record{
History: models.History{
models.HistoryEvent{
IP: net.IP{127, 0, 0, 1},
Time: exampleTime,
},
},
Status: constants.SUCCESS,
Message: "message",
Time: exampleTime,
LastBan: &exampleTime,
}

testCases := map[string]struct {
acceptHeader string
records []records.Record
responseBody string
}{
"empty records": {
records: []records.Record{},
responseBody: "[]\n",
},
"single record": {
records: []records.Record{
exampleRecord,
},
responseBody: `[{"Settings":{"a":{}},"History":[{"ip":"127.0.0.1","time":"1970-01-01T00:16:40Z"}],"Status":"success","Message":"message","Time":"1970-01-01T00:16:40Z","LastBan":"1970-01-01T00:16:40Z"}]` + "\n", // nolint:lll
},
}

for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)

// Set the settings interface on each record
for i := range testCase.records {
settings := mock_settings.NewMockSettings(ctrl)
const json = `{"a":{}}`
settings.EXPECT().MarshalJSON().Return([]byte(json), nil)
testCase.records[i].Settings = settings
}

db := mock_data.NewMockDatabase(ctrl)
db.EXPECT().SelectAll().Return(testCase.records)

handlers := &handlers{
db: db,
}

w := httptest.NewRecorder()
r := &http.Request{
Header: http.Header{
"Accept": []string{testCase.acceptHeader},
},
}

handlers.getRecords(w, r)

response := w.Result()
assert.Equal(t, http.StatusOK, response.StatusCode)
assert.Equal(t, testCase.responseBody, w.Body.String())
_ = response.Body.Close()
})
}
}
8 changes: 6 additions & 2 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/go-chi/chi/middleware"
"github.com/qdm12/ddns-updater/internal/data"
"github.com/qdm12/ddns-updater/internal/update"
"github.com/qdm12/golibs/logging"
)

type handlers struct {
Expand All @@ -18,28 +19,31 @@ type handlers struct {
db data.Database
runner update.Runner
indexTemplate *template.Template
logger logging.Logger
// Mockable functions
timeNow func() time.Time
}

func newHandler(ctx context.Context, rootURL, uiDir string,
func newHandler(ctx context.Context, rootURL, uiDir string, logger logging.Logger,
db data.Database, runner update.Runner) http.Handler {
indexTemplate := template.Must(template.ParseFiles(uiDir + "/index.html"))

handlers := &handlers{
ctx: ctx,
db: db,
indexTemplate: indexTemplate,
logger: logger,
// TODO build information
timeNow: time.Now,
runner: runner,
}

router := chi.NewRouter()

router.Use(middleware.Logger, middleware.CleanPath)
router.Use(middleware.Logger, middleware.CleanPath) // TODO use custom logging middleware

router.Get(rootURL+"/", handlers.index)
router.Get(rootURL+"/api/v1/records", handlers.getRecords)

router.Get(rootURL+"/update", handlers.update)

Expand Down
2 changes: 1 addition & 1 deletion internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type server struct {

func New(ctx context.Context, address, rootURL, uiDir string, db data.Database, logger logging.Logger,
runner update.Runner) Server {
handler := newHandler(ctx, rootURL, uiDir, db, runner)
handler := newHandler(ctx, rootURL, uiDir, logger, db, runner)
return &server{
address: address,
logger: logger,
Expand Down
6 changes: 5 additions & 1 deletion internal/settings/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type cloudflare struct {
zoneIdentifier string
proxied bool
ttl uint
matcher regex.Matcher
matcher regex.Matcher `json:"-"`
}

func NewCloudflare(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion,
Expand Down Expand Up @@ -112,6 +112,10 @@ func (c *cloudflare) BuildDomainName() string {
return buildDomainName(c.host, c.domain)
}

func (c *cloudflare) MarshalJSON() (b []byte, err error) {
return json.Marshal(c)
}

func (c *cloudflare) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", c.BuildDomainName(), c.BuildDomainName())),
Expand Down
4 changes: 4 additions & 0 deletions internal/settings/ddnss.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func (d *ddnss) BuildDomainName() string {
return buildDomainName(d.host, d.domain)
}

func (d *ddnss) MarshalJSON() (b []byte, err error) {
return json.Marshal(d)
}

func (d *ddnss) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
Expand Down
4 changes: 4 additions & 0 deletions internal/settings/digitalocean.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (d *digitalOcean) BuildDomainName() string {
return buildDomainName(d.host, d.domain)
}

func (d *digitalOcean) MarshalJSON() (b []byte, err error) {
return json.Marshal(d)
}

func (d *digitalOcean) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
Expand Down
6 changes: 5 additions & 1 deletion internal/settings/dnsomatic.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type dnsomatic struct {
username string
password string
useProviderIP bool
matcher regex.Matcher
matcher regex.Matcher `json:"-"`
}

func NewDNSOMatic(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion,
Expand Down Expand Up @@ -90,6 +90,10 @@ func (d *dnsomatic) BuildDomainName() string {
return buildDomainName(d.host, d.domain)
}

func (d *dnsomatic) MarshalJSON() (b []byte, err error) {
return json.Marshal(d)
}

func (d *dnsomatic) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
Expand Down
4 changes: 4 additions & 0 deletions internal/settings/dnspod.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (d *dnspod) BuildDomainName() string {
return buildDomainName(d.host, d.domain)
}

func (d *dnspod) MarshalJSON() (b []byte, err error) {
return json.Marshal(d)
}

func (d *dnspod) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
Expand Down
4 changes: 4 additions & 0 deletions internal/settings/dondominio.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ func (d *donDominio) BuildDomainName() string {
return buildDomainName(d.host, d.domain)
}

func (d *donDominio) MarshalJSON() (b []byte, err error) {
return json.Marshal(d)
}

func (d *donDominio) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
Expand Down
Loading