Browse Source

Fixed #31

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
3af1a1dae4
  1. 2
      handlers/handlers.go
  2. 14
      handlers/public.go
  3. 12
      handlers/public_test.go
  4. 41
      static/src/Card/Card.js
  5. 34
      static/src/Home/Home.js
  6. 38
      static/src/Lookup/Lookup.js
  7. 15
      store/store.go
  8. 14
      store/store_test.go
  9. 2
      store/util.go

2
handlers/handlers.go

@ -90,7 +90,7 @@ func (h *Handler) setHandlers() error {
protected := h.engine.Group("/api/v1/protected") protected := h.engine.Group("/api/v1/protected")
protected.Use(h.authMiddleware) protected.Use(h.authMiddleware)
protected.POST("/create", h.handleCreate) 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)))) h.engine.NoRoute(h.handleAccess, gin.WrapH(http.FileServer(FS(false))))
return nil return nil

14
handlers/public.go

@ -13,8 +13,8 @@ type URLUtil struct {
URL string `binding:"required"` URL string `binding:"required"`
} }
// handleInfo is the http handler for getting the infos // handleLookup is the http handler for getting the infos
func (h *Handler) handleInfo(c *gin.Context) { func (h *Handler) handleLookup(c *gin.Context) {
var data struct { var data struct {
ID string `binding:"required"` ID string `binding:"required"`
} }
@ -30,11 +30,13 @@ func (h *Handler) handleInfo(c *gin.Context) {
user := c.MustGet("user").(*jwtClaims) user := c.MustGet("user").(*jwtClaims)
if entry.OAuthID != user.OAuthID || entry.OAuthProvider != user.OAuthProvider { if entry.OAuthID != user.OAuthID || entry.OAuthProvider != user.OAuthProvider {
c.JSON(http.StatusOK, store.Entry{ c.JSON(http.StatusOK, store.Entry{
URL: entry.URL, Public: store.EntryPublicData{
URL: entry.Public.URL,
},
}) })
return return
} }
c.JSON(http.StatusOK, entry) c.JSON(http.StatusOK, entry.Public)
} }
// handleAccess handles the access for incoming requests // 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()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
c.Redirect(http.StatusTemporaryRedirect, entry.URL) c.Redirect(http.StatusTemporaryRedirect, entry.Public.URL)
} }
// handleCreate handles requests to create an entry // handleCreate handles requests to create an entry
@ -66,7 +68,9 @@ func (h *Handler) handleCreate(c *gin.Context) {
} }
user := c.MustGet("user").(*jwtClaims) user := c.MustGet("user").(*jwtClaims)
id, err := h.store.CreateEntry(store.Entry{ id, err := h.store.CreateEntry(store.Entry{
Public: store.EntryPublicData{
URL: data.URL, URL: data.URL,
},
RemoteAddr: c.ClientIP(), RemoteAddr: c.ClientIP(),
OAuthProvider: user.OAuthProvider, OAuthProvider: user.OAuthProvider,
OAuthID: user.OAuthID, OAuthID: user.OAuthID,

12
handlers/public_test.go

@ -90,8 +90,8 @@ func TestCreateEntry(t *testing.T) {
func TestHandleInfo(t *testing.T) { func TestHandleInfo(t *testing.T) {
t.Run("check existing entry", func(t *testing.T) { t.Run("check existing entry", func(t *testing.T) {
reqBody, err := json.Marshal(store.Entry{ reqBody, err := json.Marshal(gin.H{
URL: testURL, "URL": testURL,
}) })
if err != nil { if err != nil {
t.Fatalf("could not marshal json: %v", err) t.Fatalf("could not marshal json: %v", err)
@ -109,7 +109,7 @@ func TestHandleInfo(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("could not marshal the body: %v", err) 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 { if err != nil {
t.Fatalf("could not create request %v", err) t.Fatalf("could not create request %v", err)
} }
@ -122,7 +122,7 @@ func TestHandleInfo(t *testing.T) {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
t.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) 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 { if err = json.NewDecoder(resp.Body).Decode(&entry); err != nil {
t.Fatalf("could not unmarshal data: %v", err) 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) { 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 { if err != nil {
t.Fatalf("could not create request %v", err) 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) { 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 { if err != nil {
t.Fatalf("could not create request %v", err) t.Fatalf("could not create request %v", err)
} }

41
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 (<Card key={this.key}>
<Card.Content>
<Card.Header>
{this.props.header}
</Card.Header>
<Card.Meta>
{this.props.metaHeader}
</Card.Meta>
<Card.Description>
{this.props.description}
</Card.Description>
</Card.Content>
<Card.Content extra>
{!this.props.showInfoURL ? <div className='ui two buttons'>
<Modal closeIcon trigger={<Button icon='qrcode' content='Show QR-Code' />}>
<Modal.Header className="ui center aligned">{this.props.description}</Modal.Header>
<Modal.Content style={{ textAlign: "center" }}>
<QRCode style={{ width: "75%" }} value={this.props.description} />
</Modal.Content>
</Modal>
<Clipboard component="button" className="ui button" data-clipboard-text={this.props.description} button-title="Copy the Shortened URL to the Clipboard">
<div>
<Icon name="clipboard" />
Copy to Clipboard
</div>
</Clipboard>
</div> : <div className='ui two buttons'>
<Button icon='line chart' content='Show live tracking' />
<Button icon='clock' content='Show recent visitors' />
</div>}
</Card.Content>
</Card>)
}
};

34
static/src/Home/Home.js

@ -1,7 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Input, Segment, Form, Header, Card, Icon, Image, Button, Modal } from 'semantic-ui-react' import { Input, Segment, Form, Header, Card } from 'semantic-ui-react'
import { QRCode } from 'react-qr-svg';
import Clipboard from 'react-clipboard.js'; import CustomCard from '../Card/Card'
export default class HomeComponent extends Component { export default class HomeComponent extends Component {
handleURLChange = (e, { value }) => this.url = value handleURLChange = (e, { value }) => this.url = value
@ -43,33 +43,7 @@ export default class HomeComponent extends Component {
</Form> </Form>
</Segment> </Segment>
<Card.Group itemsPerRow="2"> <Card.Group itemsPerRow="2">
{links.map((link, i) => <Card key={i}> {links.map((link, i) => <CustomCard key={i} header={new URL(link[1]).hostname} metaHeader={link[1]} description={link[0]} />)}
<Card.Content>
<Card.Header>
{new URL(link[1]).hostname}
</Card.Header>
<Card.Meta>
{link[1]}
</Card.Meta>
<Card.Description>
{link[0]}
</Card.Description>
</Card.Content>
<Card.Content extra>
<div className='ui two buttons'>
<Modal closeIcon trigger={<Button icon='qrcode' content='Show QR-Code' />}>
<Modal.Header className="ui center aligned">{link[0]}</Modal.Header>
<Modal.Content style={{ textAlign: "center" }}>
<QRCode style={{ width: "75%" }} value={link[0]} />
</Modal.Content>
</Modal>
<Clipboard component="button" className="ui button" data-clipboard-text={link[0]} button-title="Copy the Shortened URL to Clipboard">
<Icon name="clipboard" />
Copy to Clipboard
</Clipboard>
</div>
</Card.Content>
</Card>)}
</Card.Group> </Card.Group>
</div > </div >
) )

38
static/src/Lookup/Lookup.js

@ -1,17 +1,51 @@
import React, { Component } from 'react' 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 { 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() { render() {
const { links } = this.state
return ( return (
<div>
<Segment raised> <Segment raised>
<Header size='huge'>URL Lookup</Header> <Header size='huge'>URL Lookup</Header>
<Form onSubmit={this.handleURLSubmit} autoComplete="off"> <Form onSubmit={this.handleURLSubmit} autoComplete="off">
<Form.Field> <Form.Field>
<Input required size='big' ref={input => this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin+"/..."} /> <Input required size='big' ref={input => this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin + "/..."} />
</Form.Field> </Form.Field>
</Form> </Form>
</Segment> </Segment>
<Card.Group itemsPerRow="2">
{links.map((link, i) => <CustomCard key={i} header={new URL(link[0]).hostname} metaHeader={link[1]} description={link[0]} showInfoURL/>)}
</Card.Group>
</div>
) )
} }
}; };

15
store/store.go

@ -23,10 +23,15 @@ type Store struct {
// Entry is the data set which is stored in the DB as JSON // Entry is the data set which is stored in the DB as JSON
type Entry struct { type Entry struct {
URL, OAuthProvider, OAuthID string OAuthProvider, OAuthID string
VisitCount int
RemoteAddr string `json:",omitempty"` RemoteAddr string `json:",omitempty"`
Public EntryPublicData
}
type EntryPublicData struct {
CreatedOn, LastVisit time.Time CreatedOn, LastVisit time.Time
VisitCount int
URL string
} }
// ErrNoEntryFound is returned when no entry to a id is found // 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 { if err != nil {
return errors.Wrap(err, "could not get entry by ID") return errors.Wrap(err, "could not get entry by ID")
} }
entry.VisitCount++ entry.Public.VisitCount++
entry.LastVisit = time.Now() entry.Public.LastVisit = time.Now()
raw, err := json.Marshal(entry) raw, err := json.Marshal(entry)
if err != nil { if err != nil {
return err return err
@ -112,7 +117,7 @@ func (s *Store) GetEntryByIDRaw(id string) ([]byte, error) {
// CreateEntry creates a new record and returns his short id // CreateEntry creates a new record and returns his short id
func (s *Store) CreateEntry(entry Entry) (string, error) { func (s *Store) CreateEntry(entry Entry) (string, error) {
if !govalidator.IsURL(entry.URL) { if !govalidator.IsURL(entry.Public.URL) {
return "", ErrNoValidURL return "", ErrNoValidURL
} }
// try it 10 times to make a short URL // try it 10 times to make a short URL

14
store/store_test.go

@ -74,7 +74,11 @@ func TestCreateEntry(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
for i := 1; i <= 100; i++ { 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 { if err != nil && err != ErrGeneratingIDFailed {
t.Fatalf("unexpected error during creating entry: %v", err) 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) t.Fatalf("could not create store: %v", err)
} }
defer cleanup(store) 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 { if err != nil {
t.Fatalf("could not create entry: %v", err) t.Fatalf("could not create entry: %v", err)
} }
@ -118,7 +126,7 @@ func TestIncreaseVisitCounter(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("could not get entry by id: %v", err) 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") 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" errIDIsEmpty := "could not get entry by ID: the given ID is empty"

2
store/util.go

@ -33,7 +33,7 @@ func (s *Store) createEntry(entry Entry) (string, error) {
if err != nil { if err != nil {
return "", errors.Wrap(err, "could not generate random string") return "", errors.Wrap(err, "could not generate random string")
} }
entry.CreatedOn = time.Now() entry.Public.CreatedOn = time.Now()
raw, err := json.Marshal(entry) raw, err := json.Marshal(entry)
if err != nil { if err != nil {
return "", err return "", err

Loading…
Cancel
Save