Skip to content
This repository was archived by the owner on Mar 16, 2021. It is now read-only.

Commit 82e21aa

Browse files
author
Jeff Billimek
committed
MVP for (no auth required) admin view
1 parent 168565c commit 82e21aa

File tree

9 files changed

+176
-1
lines changed

9 files changed

+176
-1
lines changed

internal/handlers/handlers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ func (h *Handler) setHandlers() error {
158158
protected.POST("/create", h.handleCreate)
159159
protected.POST("/lookup", h.handleLookup)
160160
protected.GET("/recent", h.handleRecent)
161+
protected.GET("/admin", h.handleAdmin)
161162
protected.POST("/visitors", h.handleGetVisitors)
162163

163164
h.engine.GET("/api/v1/info", h.handleInfo)

internal/handlers/public.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ func (h *Handler) handleRecent(c *gin.Context) {
180180
c.JSON(http.StatusOK, entries)
181181
}
182182

183+
func (h *Handler) handleAdmin(c *gin.Context) {
184+
entries, err := h.store.GetAllUserEntries()
185+
if err != nil {
186+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
187+
return
188+
}
189+
for k, entry := range entries {
190+
mac := hmac.New(sha512.New, util.GetPrivateKey())
191+
if _, err := mac.Write([]byte(k)); err != nil {
192+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
193+
}
194+
entry.DeletionURL = fmt.Sprintf("%s/d/%s/%s", h.getURLOrigin(c), k, url.QueryEscape(base64.RawURLEncoding.EncodeToString(mac.Sum(nil))))
195+
entries[k] = entry
196+
}
197+
c.JSON(http.StatusOK, entries)
198+
}
199+
183200
func (h *Handler) handleDelete(c *gin.Context) {
184201
givenHmac, err := base64.RawURLEncoding.DecodeString(c.Param("hash"))
185202
if err != nil {

internal/stores/boltdb/boltdb.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,26 @@ func (b *BoltStore) GetUserEntries(userIdentifier string) (map[string]shared.Ent
176176
return entries, errors.Wrap(err, "could not update db")
177177
}
178178

179+
// GetAllUserEntries returns all user entries
180+
func (b *BoltStore) GetAllUserEntries() (map[string]shared.Entry, error) {
181+
entries := map[string]shared.Entry{}
182+
err := b.db.Update(func(tx *bolt.Tx) error {
183+
bucket, err := tx.CreateBucketIfNotExists(shortedIDsToUserBucket)
184+
if err != nil {
185+
return errors.Wrap(err, "could not create bucket")
186+
}
187+
return bucket.ForEach(func(k, v []byte) error {
188+
entry, err := b.GetEntryByID(string(k))
189+
if err != nil {
190+
return errors.Wrap(err, "could not get entry")
191+
}
192+
entries[string(k)] = *entry
193+
return nil
194+
})
195+
})
196+
return entries, errors.Wrap(err, "could not update db")
197+
}
198+
179199
// RegisterVisitor saves the visitor in the database
180200
func (b *BoltStore) RegisterVisitor(id, visitID string, visitor shared.Visitor) error {
181201
err := b.db.Update(func(tx *bolt.Tx) error {

internal/stores/redis/redis.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,32 @@ func (r *Store) GetEntryByID(id string) (*shared.Entry, error) {
274274
func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry, error) {
275275
logrus.Debugf("Getting all entries for user %s", userIdentifier)
276276
entries := map[string]shared.Entry{}
277+
key := userToEntriesPrefix + userIdentifier
278+
result := r.c.SMembers(key)
279+
if result.Err() != nil {
280+
msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, result.Err())
281+
logrus.Errorf(msg)
282+
return nil, errors.Wrap(result.Err(), msg)
283+
}
284+
for _, v := range result.Val() {
285+
logrus.Debugf("got entry: %s", v)
286+
entry, err := r.GetEntryByID(string(v))
287+
if err != nil {
288+
msg := fmt.Sprintf("Could not get entry '%s': %s", v, err)
289+
logrus.Warn(msg)
290+
} else {
291+
entries[string(v)] = *entry
292+
}
293+
}
294+
logrus.Debugf("all out of entries")
295+
return entries, nil
296+
}
277297

298+
// GetAllUserEntries returns all entries for all users, in the
299+
// form of a map of path->shared.Entry
300+
func (r *Store) GetAllUserEntries() (map[string]shared.Entry, error) {
301+
logrus.Debugf("Getting all entries for all users")
302+
entries := map[string]shared.Entry{}
278303
users := r.c.Keys(userToEntriesPrefix + "*")
279304
for _, v := range users.Val() {
280305
logrus.Debugf("got userEntry: %s", v)
@@ -285,7 +310,7 @@ func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry,
285310
// result := r.c.Keys(entryPathPrefix + user)
286311

287312
if result.Err() != nil {
288-
msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, result.Err())
313+
msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", key, result.Err())
289314
logrus.Errorf(msg)
290315
return nil, errors.Wrap(result.Err(), msg)
291316
}

internal/stores/shared/shared.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Storage interface {
1414
IncreaseVisitCounter(string) error
1515
CreateEntry(Entry, string, string) error
1616
GetUserEntries(string) (map[string]Entry, error)
17+
GetAllUserEntries() (map[string]Entry, error)
1718
RegisterVisitor(string, string, Visitor) error
1819
Close() error
1920
}

internal/stores/store.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ func (s *Store) GetUserEntries(oAuthProvider, oAuthID string) (map[string]shared
158158
return entries, nil
159159
}
160160

161+
// GetAllUserEntries returns all the shorted URL entries of an user
162+
func (s *Store) GetAllUserEntries() (map[string]shared.Entry, error) {
163+
entries, err := s.storage.GetAllUserEntries()
164+
if err != nil {
165+
return nil, errors.Wrap(err, "could not get all entries")
166+
}
167+
return entries, nil
168+
}
169+
161170
func getUserIdentifier(oAuthProvider, oAuthID string) string {
162171
return oAuthProvider + oAuthID
163172
}

web/src/Admin/Admin.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { Component } from 'react'
2+
import { Container, Button, Icon } from 'semantic-ui-react'
3+
import Moment from 'react-moment';
4+
import ReactTable from 'react-table'
5+
import 'react-table/react-table.css'
6+
7+
import util from '../util/util'
8+
9+
export default class AllEntriesComponent extends Component {
10+
state = {
11+
allEntries: []
12+
}
13+
14+
componentDidMount() {
15+
this.getAllURLs()
16+
}
17+
18+
getAllURLs = () => {
19+
util.getAllURLs(allEntries => {
20+
let parsed = [];
21+
for (let key in allEntries) {
22+
allEntries[key].ID = key;
23+
parsed.push(allEntries[key]);
24+
}
25+
this.setState({ allEntries: parsed })
26+
})
27+
}
28+
29+
onRowClick(id) {
30+
this.props.history.push(`/visitors/${id}`)
31+
}
32+
33+
onEntryDeletion(deletionURL) {
34+
util.deleteEntry(deletionURL, this.getAllURLs)
35+
}
36+
37+
render() {
38+
const { allEntries } = this.state
39+
40+
const columns = [{
41+
Header: 'Original URL',
42+
accessor: "Public.URL"
43+
}, {
44+
Header: 'Created',
45+
accessor: 'Public.CreatedOn',
46+
Cell: props => <Moment fromNow>{props.value}</Moment>
47+
}, {
48+
Header: 'Short URL',
49+
accessor: "ID",
50+
Cell: props => `${window.location.origin}/${props.value}`
51+
}, {
52+
Header: 'Visitor count',
53+
accessor: "Public.VisitCount"
54+
55+
}, {
56+
Header: 'Delete',
57+
accessor: 'DeletionURL',
58+
Cell: props => <Button animated='vertical' onClick={this.onEntryDeletion.bind(this, props.value)}>
59+
<Button.Content hidden>Delete</Button.Content>
60+
<Button.Content visible>
61+
<Icon name='trash' />
62+
</Button.Content>
63+
</Button>,
64+
style: { textAlign: "center" }
65+
}]
66+
67+
return (
68+
<Container>
69+
<ReactTable data={allEntries} columns={columns} getTdProps={(state, rowInfo, column, instance) => {
70+
return {
71+
onClick: (e, handleOriginal) => {
72+
if (handleOriginal) {
73+
handleOriginal()
74+
}
75+
if (!rowInfo) {
76+
return
77+
}
78+
if (column.id === "DeletionURL") {
79+
return
80+
}
81+
this.onRowClick(rowInfo.row.ID)
82+
}
83+
}
84+
}} />
85+
</Container>
86+
)
87+
}
88+
}

web/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Home from './Home/Home'
1111
import ShareX from './ShareX/ShareX'
1212
import Lookup from './Lookup/Lookup'
1313
import Recent from './Recent/Recent'
14+
import Admin from './Admin/Admin'
1415
import Visitors from './Visitors/Visitors'
1516

1617
import util from './util/util'
@@ -180,6 +181,7 @@ export default class BaseComponent extends Component {
180181
<Route path="/ShareX" component={ShareX} />
181182
<Route path="/Lookup" component={Lookup} />
182183
<Route path="/recent" component={Recent} />
184+
<Route path="/admin" component={Admin} />
183185
<Route path="/visitors/:id" component={Visitors} />
184186
</Container>
185187
</HashRouter>

web/src/util/util.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,16 @@ export default class UtilHelper {
5656
.then(res => cbSucc ? cbSucc(res) : null)
5757
.catch(e => this._reportError(e, "recent"))
5858
}
59+
static getAllURLs(cbSucc) {
60+
fetch('/api/v1/protected/admin', {
61+
credentials: "include",
62+
headers: {
63+
'Authorization': window.localStorage.getItem('token'),
64+
'Content-Type': 'application/json'
65+
}
66+
})
67+
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
68+
.then(res => cbSucc ? cbSucc(res) : null)
69+
.catch(e => this._reportError(e, "recent"))
70+
}
5971
}

0 commit comments

Comments
 (0)