Skip to content
This repository was archived by the owner on Mar 16, 2021. It is now read-only.
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
1 change: 1 addition & 0 deletions internal/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func (h *Handler) setHandlers() error {
protected.POST("/create", h.handleCreate)
protected.POST("/lookup", h.handleLookup)
protected.GET("/recent", h.handleRecent)
protected.GET("/admin", h.handleAdmin)
protected.POST("/visitors", h.handleGetVisitors)

h.engine.GET("/api/v1/info", h.handleInfo)
Expand Down
17 changes: 17 additions & 0 deletions internal/handlers/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ func (h *Handler) handleRecent(c *gin.Context) {
c.JSON(http.StatusOK, entries)
}

func (h *Handler) handleAdmin(c *gin.Context) {
entries, err := h.store.GetAllUserEntries()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
for k, entry := range entries {
mac := hmac.New(sha512.New, util.GetPrivateKey())
if _, err := mac.Write([]byte(k)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
entry.DeletionURL = fmt.Sprintf("%s/d/%s/%s", h.getURLOrigin(c), k, url.QueryEscape(base64.RawURLEncoding.EncodeToString(mac.Sum(nil))))
entries[k] = entry
}
c.JSON(http.StatusOK, entries)
}

func (h *Handler) handleDelete(c *gin.Context) {
givenHmac, err := base64.RawURLEncoding.DecodeString(c.Param("hash"))
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions internal/stores/boltdb/boltdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ func (b *BoltStore) GetUserEntries(userIdentifier string) (map[string]shared.Ent
return entries, errors.Wrap(err, "could not update db")
}

// GetAllUserEntries returns all user entries
func (b *BoltStore) GetAllUserEntries() (map[string]shared.Entry, error) {
entries := map[string]shared.Entry{}
err := b.db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(shortedIDsToUserBucket)
if err != nil {
return errors.Wrap(err, "could not create bucket")
}
return bucket.ForEach(func(k, v []byte) error {
entry, err := b.GetEntryByID(string(k))
if err != nil {
return errors.Wrap(err, "could not get entry")
}
entries[string(k)] = *entry
return nil
})
})
return entries, errors.Wrap(err, "could not update db")
}

// RegisterVisitor saves the visitor in the database
func (b *BoltStore) RegisterVisitor(id, visitID string, visitor shared.Visitor) error {
err := b.db.Update(func(tx *bolt.Tx) error {
Expand Down
30 changes: 30 additions & 0 deletions internal/stores/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,36 @@ func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry,
return entries, nil
}

// GetAllUserEntries returns all entries for all users, in the
// form of a map of path->shared.Entry
func (r *Store) GetAllUserEntries() (map[string]shared.Entry, error) {
logrus.Debugf("Getting all entries for all users")
entries := map[string]shared.Entry{}
users := r.c.Keys(userToEntriesPrefix + "*")
for _, v := range users.Val() {
logrus.Debugf("got userEntry: %s", v)
key := v
result := r.c.SMembers(key)
if result.Err() != nil {
msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", key, result.Err())
logrus.Errorf(msg)
return nil, errors.Wrap(result.Err(), msg)
}
for _, v := range result.Val() {
logrus.Debugf("got entry: %s", v)
entry, err := r.GetEntryByID(string(v))
if err != nil {
msg := fmt.Sprintf("Could not get entry '%s': %s", v, err)
logrus.Warn(msg)
} else {
entries[string(v)] = *entry
}
}
logrus.Debugf("all out of entries")
}
return entries, nil
}

// RegisterVisitor adds a shared.Visitor to the list of visits for a path.
func (r *Store) RegisterVisitor(id, visitID string, visitor shared.Visitor) error {
data, err := json.Marshal(visitor)
Expand Down
1 change: 1 addition & 0 deletions internal/stores/shared/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Storage interface {
IncreaseVisitCounter(string) error
CreateEntry(Entry, string, string) error
GetUserEntries(string) (map[string]Entry, error)
GetAllUserEntries() (map[string]Entry, error)
RegisterVisitor(string, string, Visitor) error
Close() error
}
Expand Down
9 changes: 9 additions & 0 deletions internal/stores/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ func (s *Store) GetUserEntries(oAuthProvider, oAuthID string) (map[string]shared
return entries, nil
}

// GetAllUserEntries returns all the shorted URL entries of an user
func (s *Store) GetAllUserEntries() (map[string]shared.Entry, error) {
entries, err := s.storage.GetAllUserEntries()
if err != nil {
return nil, errors.Wrap(err, "could not get all entries")
}
return entries, nil
}

func getUserIdentifier(oAuthProvider, oAuthID string) string {
return oAuthProvider + oAuthID
}
Expand Down
94 changes: 94 additions & 0 deletions web/src/Admin/Admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { Component } from 'react'
import { Container, Button, Icon } from 'semantic-ui-react'
import Moment from 'react-moment';
import ReactTable from 'react-table'
import 'react-table/react-table.css'

import util from '../util/util'

export default class AllEntriesComponent extends Component {
state = {
allEntries: [],
displayURL: window.location.origin
}

componentDidMount() {
this.getAllURLs()
fetch("/displayurl")
.then(response => response.json())
.then(data => this.setState({displayURL: data}));
}

getAllURLs = () => {
util.getAllURLs(allEntries => {
let parsed = [];
for (let key in allEntries) {
if ({}.hasOwnProperty.call(allEntries, key)) {
allEntries[key].ID = key;
parsed.push(allEntries[key]);
}
}
this.setState({ allEntries: parsed })
})
}

onRowClick(id) {
this.props.history.push(`/visitors/${id}`)
}

onEntryDeletion(deletionURL) {
util.deleteEntry(deletionURL, this.getAllURLs)
}

render() {
const { allEntries } = this.state

const columns = [{
Header: 'Original URL',
accessor: "Public.URL"
}, {
Header: 'Created',
accessor: 'Public.CreatedOn',
Cell: props => <Moment fromNow>{props.value}</Moment>
}, {
Header: 'Short URL',
accessor: "ID",
Cell: props => `${this.state.displayURL}/${props.value}`
}, {
Header: 'Visitor count',
accessor: "Public.VisitCount"

}, {
Header: 'Delete',
accessor: 'DeletionURL',
Cell: props => <Button animated='vertical' onClick={this.onEntryDeletion.bind(this, props.value)}>
<Button.Content hidden>Delete</Button.Content>
<Button.Content visible>
<Icon name='trash' />
</Button.Content>
</Button>,
style: { textAlign: "center" }
}]

return (
<Container>
<ReactTable data={allEntries} columns={columns} getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: (e, handleOriginal) => {
if (handleOriginal) {
handleOriginal()
}
if (!rowInfo) {
return
}
if (column.id === "DeletionURL") {
return
}
this.onRowClick(rowInfo.row.ID)
}
}
}} />
</Container>
)
}
}
2 changes: 2 additions & 0 deletions web/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Home from './Home/Home'
import ShareX from './ShareX/ShareX'
import Lookup from './Lookup/Lookup'
import Recent from './Recent/Recent'
import Admin from './Admin/Admin'
import Visitors from './Visitors/Visitors'

import util from './util/util'
Expand Down Expand Up @@ -186,6 +187,7 @@ export default class BaseComponent extends Component {
<Route path="/ShareX" component={ShareX} />
<Route path="/Lookup" component={Lookup} />
<Route path="/recent" component={Recent} />
<Route path="/admin" component={Admin} />
<Route path="/visitors/:id" component={Visitors} />
</Container>
</HashRouter>
Expand Down
12 changes: 12 additions & 0 deletions web/src/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,16 @@ export default class UtilHelper {
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
.catch(e => this._reportError(e, "getDisplayURL"))
}
static getAllURLs(cbSucc) {
fetch('/api/v1/protected/admin', {
credentials: "include",
headers: {
'Authorization': window.localStorage.getItem('token'),
'Content-Type': 'application/json'
}
})
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(res => cbSucc ? cbSucc(res) : null)
.catch(e => this._reportError(e, "recent"))
}
}