From 3af1a1dae4cc75cb7c58f10de6bc93372b2c6797 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sat, 11 Nov 2017 13:06:15 +0100 Subject: [PATCH] Fixed #31 --- handlers/handlers.go | 2 +- handlers/public.go | 16 +++++++----- handlers/public_test.go | 12 ++++----- static/src/Card/Card.js | 41 +++++++++++++++++++++++++++++ static/src/Home/Home.js | 34 +++--------------------- static/src/Lookup/Lookup.js | 52 ++++++++++++++++++++++++++++++------- store/store.go | 19 +++++++++----- store/store_test.go | 14 +++++++--- store/util.go | 2 +- 9 files changed, 129 insertions(+), 63 deletions(-) create mode 100644 static/src/Card/Card.js diff --git a/handlers/handlers.go b/handlers/handlers.go index 6f462c1..c446817 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -90,7 +90,7 @@ func (h *Handler) setHandlers() error { protected := h.engine.Group("/api/v1/protected") protected.Use(h.authMiddleware) protected.POST("/create", h.handleCreate) - protected.POST("/info", h.handleInfo) + protected.POST("/lookup", h.handleLookup) h.engine.NoRoute(h.handleAccess, gin.WrapH(http.FileServer(FS(false)))) return nil diff --git a/handlers/public.go b/handlers/public.go index 8e53a3a..7274e57 100644 --- a/handlers/public.go +++ b/handlers/public.go @@ -13,8 +13,8 @@ type URLUtil struct { URL string `binding:"required"` } -// handleInfo is the http handler for getting the infos -func (h *Handler) handleInfo(c *gin.Context) { +// handleLookup is the http handler for getting the infos +func (h *Handler) handleLookup(c *gin.Context) { var data struct { ID string `binding:"required"` } @@ -30,11 +30,13 @@ func (h *Handler) handleInfo(c *gin.Context) { user := c.MustGet("user").(*jwtClaims) if entry.OAuthID != user.OAuthID || entry.OAuthProvider != user.OAuthProvider { c.JSON(http.StatusOK, store.Entry{ - URL: entry.URL, + Public: store.EntryPublicData{ + URL: entry.Public.URL, + }, }) return } - c.JSON(http.StatusOK, entry) + c.JSON(http.StatusOK, entry.Public) } // handleAccess handles the access for incoming requests @@ -54,7 +56,7 @@ func (h *Handler) handleAccess(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.Redirect(http.StatusTemporaryRedirect, entry.URL) + c.Redirect(http.StatusTemporaryRedirect, entry.Public.URL) } // handleCreate handles requests to create an entry @@ -66,7 +68,9 @@ func (h *Handler) handleCreate(c *gin.Context) { } user := c.MustGet("user").(*jwtClaims) id, err := h.store.CreateEntry(store.Entry{ - URL: data.URL, + Public: store.EntryPublicData{ + URL: data.URL, + }, RemoteAddr: c.ClientIP(), OAuthProvider: user.OAuthProvider, OAuthID: user.OAuthID, diff --git a/handlers/public_test.go b/handlers/public_test.go index 5daf02c..6138e2d 100644 --- a/handlers/public_test.go +++ b/handlers/public_test.go @@ -90,8 +90,8 @@ func TestCreateEntry(t *testing.T) { func TestHandleInfo(t *testing.T) { t.Run("check existing entry", func(t *testing.T) { - reqBody, err := json.Marshal(store.Entry{ - URL: testURL, + reqBody, err := json.Marshal(gin.H{ + "URL": testURL, }) if err != nil { t.Fatalf("could not marshal json: %v", err) @@ -109,7 +109,7 @@ func TestHandleInfo(t *testing.T) { if err != nil { t.Fatalf("could not marshal the body: %v", err) } - req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/info", bytes.NewBuffer(body)) + req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/lookup", bytes.NewBuffer(body)) if err != nil { t.Fatalf("could not create request %v", err) } @@ -122,7 +122,7 @@ func TestHandleInfo(t *testing.T) { if resp.StatusCode != http.StatusOK { t.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) } - var entry store.Entry + var entry store.EntryPublicData if err = json.NewDecoder(resp.Body).Decode(&entry); err != nil { t.Fatalf("could not unmarshal data: %v", err) } @@ -131,7 +131,7 @@ func TestHandleInfo(t *testing.T) { } }) t.Run("invalid body", func(t *testing.T) { - req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/info", bytes.NewBuffer(nil)) + req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/lookup", bytes.NewBuffer(nil)) if err != nil { t.Fatalf("could not create request %v", err) } @@ -157,7 +157,7 @@ func TestHandleInfo(t *testing.T) { } }) t.Run("no ID provided", func(t *testing.T) { - req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/info", bytes.NewBufferString("{}")) + req, err := http.NewRequest("POST", server.URL+"/api/v1/protected/lookup", bytes.NewBufferString("{}")) if err != nil { t.Fatalf("could not create request %v", err) } diff --git a/static/src/Card/Card.js b/static/src/Card/Card.js new file mode 100644 index 0000000..d2d3572 --- /dev/null +++ b/static/src/Card/Card.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react' +import { Card, Icon, Button, Modal } from 'semantic-ui-react' +import { QRCode } from 'react-qr-svg'; +import Clipboard from 'react-clipboard.js'; + +export default class CardComponent extends Component { + render() { + return ( + + + {this.props.header} + + + {this.props.metaHeader} + + + {this.props.description} + + + + {!this.props.showInfoURL ?
+ }> + {this.props.description} + + + + + +
+ + Copy to Clipboard +
+
+
:
+
} +
+
) + } +}; \ No newline at end of file diff --git a/static/src/Home/Home.js b/static/src/Home/Home.js index 0f31ef0..a1cc9f6 100644 --- a/static/src/Home/Home.js +++ b/static/src/Home/Home.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' -import { Input, Segment, Form, Header, Card, Icon, Image, Button, Modal } from 'semantic-ui-react' -import { QRCode } from 'react-qr-svg'; -import Clipboard from 'react-clipboard.js'; +import { Input, Segment, Form, Header, Card } from 'semantic-ui-react' + +import CustomCard from '../Card/Card' export default class HomeComponent extends Component { handleURLChange = (e, { value }) => this.url = value @@ -43,33 +43,7 @@ export default class HomeComponent extends Component { - {links.map((link, i) => - - - {new URL(link[1]).hostname} - - - {link[1]} - - - {link[0]} - - - -
- }> - {link[0]} - - - - - - - Copy to Clipboard - -
-
-
)} + {links.map((link, i) => )}
) diff --git a/static/src/Lookup/Lookup.js b/static/src/Lookup/Lookup.js index 50a033f..6366e7f 100644 --- a/static/src/Lookup/Lookup.js +++ b/static/src/Lookup/Lookup.js @@ -1,17 +1,51 @@ import React, { Component } from 'react' -import { Segment, Header, Form, Input } from 'semantic-ui-react' +import { Segment, Header, Form, Input, Card } from 'semantic-ui-react' + +import CustomCard from '../Card/Card' export default class LookupComponent extends Component { + state = { + links: [] + } + handleURLChange = (e, { value }) => this.url = value + handleURLSubmit = () => { + let id = this.url.replace(window.location.origin + "/", "") + fetch("/api/v1/protected/lookup", { + method: "POST", + body: JSON.stringify({ + ID: id + }), + headers: { + 'Authorization': window.localStorage.getItem('token'), + 'Content-Type': 'application/json' + } + }).then(res => res.ok ? res.json() : Promise.reject(res.json())) + .then(res => this.setState({ + links: [...this.state.links, [ + res.URL, + this.url, + this.VisitCount, + res.CratedOn, + res.LastVisit + ]] + })) + } render() { + const { links } = this.state return ( - -
URL Lookup
-
- - this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin+"/..."} /> - -
-
+
+ +
URL Lookup
+
+ + this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin + "/..."} /> + +
+
+ + {links.map((link, i) => )} + +
) } }; diff --git a/store/store.go b/store/store.go index b353867..127a2c7 100644 --- a/store/store.go +++ b/store/store.go @@ -23,10 +23,15 @@ type Store struct { // Entry is the data set which is stored in the DB as JSON type Entry struct { - URL, OAuthProvider, OAuthID string - VisitCount int - RemoteAddr string `json:",omitempty"` - CreatedOn, LastVisit time.Time + OAuthProvider, OAuthID string + RemoteAddr string `json:",omitempty"` + Public EntryPublicData +} + +type EntryPublicData struct { + CreatedOn, LastVisit time.Time + VisitCount int + URL string } // ErrNoEntryFound is returned when no entry to a id is found @@ -82,8 +87,8 @@ func (s *Store) IncreaseVisitCounter(id string) error { if err != nil { return errors.Wrap(err, "could not get entry by ID") } - entry.VisitCount++ - entry.LastVisit = time.Now() + entry.Public.VisitCount++ + entry.Public.LastVisit = time.Now() raw, err := json.Marshal(entry) if err != nil { return err @@ -112,7 +117,7 @@ func (s *Store) GetEntryByIDRaw(id string) ([]byte, error) { // CreateEntry creates a new record and returns his short id func (s *Store) CreateEntry(entry Entry) (string, error) { - if !govalidator.IsURL(entry.URL) { + if !govalidator.IsURL(entry.Public.URL) { return "", ErrNoValidURL } // try it 10 times to make a short URL diff --git a/store/store_test.go b/store/store_test.go index f158c50..8868333 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -74,7 +74,11 @@ func TestCreateEntry(t *testing.T) { t.Fatalf("unexpected error: %v", err) } for i := 1; i <= 100; i++ { - _, err := store.CreateEntry(Entry{URL: "https://golang.org/"}) + _, err := store.CreateEntry(Entry{ + Public: EntryPublicData{ + URL: "https://golang.org/", + }, + }) if err != nil && err != ErrGeneratingIDFailed { t.Fatalf("unexpected error during creating entry: %v", err) } @@ -103,7 +107,11 @@ func TestIncreaseVisitCounter(t *testing.T) { t.Fatalf("could not create store: %v", err) } defer cleanup(store) - id, err := store.CreateEntry(Entry{URL: "https://golang.org/"}) + id, err := store.CreateEntry(Entry{ + Public: EntryPublicData{ + URL: "https://golang.org/", + }, + }) if err != nil { t.Fatalf("could not create entry: %v", err) } @@ -118,7 +126,7 @@ func TestIncreaseVisitCounter(t *testing.T) { if err != nil { t.Fatalf("could not get entry by id: %v", err) } - if entryBeforeInc.VisitCount+1 != entryAfterInc.VisitCount { + if entryBeforeInc.Public.VisitCount+1 != entryAfterInc.Public.VisitCount { t.Fatalf("the increasement was not successful, the visit count is not correct") } errIDIsEmpty := "could not get entry by ID: the given ID is empty" diff --git a/store/util.go b/store/util.go index b2daa4b..949285a 100644 --- a/store/util.go +++ b/store/util.go @@ -33,7 +33,7 @@ func (s *Store) createEntry(entry Entry) (string, error) { if err != nil { return "", errors.Wrap(err, "could not generate random string") } - entry.CreatedOn = time.Now() + entry.Public.CreatedOn = time.Now() raw, err := json.Marshal(entry) if err != nil { return "", err