From 1c66063639e370c1e473425c0d5f91f00d99453c Mon Sep 17 00:00:00 2001 From: Jeff Billimek Date: Fri, 10 Aug 2018 08:18:51 -0400 Subject: [PATCH 1/4] MVP for making all links visible --- internal/stores/redis/redis.go | 41 +++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/internal/stores/redis/redis.go b/internal/stores/redis/redis.go index 18d92c90..ba7615b2 100644 --- a/internal/stores/redis/redis.go +++ b/internal/stores/redis/redis.go @@ -274,24 +274,33 @@ func (r *Store) GetEntryByID(id string) (*shared.Entry, error) { func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry, error) { logrus.Debugf("Getting all entries for user %s", userIdentifier) entries := map[string]shared.Entry{} - key := userToEntriesPrefix + userIdentifier - result := r.c.SMembers(key) - if result.Err() != nil { - msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, 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 + + users := r.c.Keys(userToEntriesPrefix + "*") + for _, v := range users.Val() { + logrus.Debugf("got userEntry: %s", v) + // user, err := r.GetEntryByID(string(v)) + + key := v + result := r.c.SMembers(key) + // result := r.c.Keys(entryPathPrefix + user) + + if result.Err() != nil { + msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, 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") } - logrus.Debugf("all out of entries") return entries, nil } From 9964335a715624a17b9f3895db1ce53df0a7acd0 Mon Sep 17 00:00:00 2001 From: Jeff Billimek Date: Mon, 13 Aug 2018 16:42:31 -0400 Subject: [PATCH 2/4] MVP for (no auth required) admin view --- internal/handlers/handlers.go | 1 + internal/handlers/public.go | 17 ++++++ internal/stores/boltdb/boltdb.go | 20 ++++++++ internal/stores/redis/redis.go | 27 +++++++++- internal/stores/shared/shared.go | 1 + internal/stores/store.go | 9 ++++ web/src/Admin/Admin.js | 88 ++++++++++++++++++++++++++++++++ web/src/index.js | 2 + web/src/util/util.js | 12 +++++ 9 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 web/src/Admin/Admin.js diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 3103e8f6..b60fa370 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -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) diff --git a/internal/handlers/public.go b/internal/handlers/public.go index d76835d3..9f277dea 100644 --- a/internal/handlers/public.go +++ b/internal/handlers/public.go @@ -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 { diff --git a/internal/stores/boltdb/boltdb.go b/internal/stores/boltdb/boltdb.go index 99fa9e85..f264149f 100644 --- a/internal/stores/boltdb/boltdb.go +++ b/internal/stores/boltdb/boltdb.go @@ -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 { diff --git a/internal/stores/redis/redis.go b/internal/stores/redis/redis.go index ba7615b2..90516590 100644 --- a/internal/stores/redis/redis.go +++ b/internal/stores/redis/redis.go @@ -274,7 +274,32 @@ func (r *Store) GetEntryByID(id string) (*shared.Entry, error) { func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry, error) { logrus.Debugf("Getting all entries for user %s", userIdentifier) entries := map[string]shared.Entry{} + key := userToEntriesPrefix + userIdentifier + result := r.c.SMembers(key) + if result.Err() != nil { + msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, 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 +} +// 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) @@ -285,7 +310,7 @@ func (r *Store) GetUserEntries(userIdentifier string) (map[string]shared.Entry, // result := r.c.Keys(entryPathPrefix + user) if result.Err() != nil { - msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", userIdentifier, result.Err()) + 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) } diff --git a/internal/stores/shared/shared.go b/internal/stores/shared/shared.go index eca88686..8131384f 100644 --- a/internal/stores/shared/shared.go +++ b/internal/stores/shared/shared.go @@ -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 } diff --git a/internal/stores/store.go b/internal/stores/store.go index 64745a34..870ffd29 100644 --- a/internal/stores/store.go +++ b/internal/stores/store.go @@ -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 } diff --git a/web/src/Admin/Admin.js b/web/src/Admin/Admin.js new file mode 100644 index 00000000..2ddab119 --- /dev/null +++ b/web/src/Admin/Admin.js @@ -0,0 +1,88 @@ +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: [] + } + + componentDidMount() { + this.getAllURLs() + } + + getAllURLs = () => { + util.getAllURLs(allEntries => { + let parsed = []; + for (let key in allEntries) { + 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 => {props.value} + }, { + Header: 'Short URL', + accessor: "ID", + Cell: props => `${window.location.origin}/${props.value}` + }, { + Header: 'Visitor count', + accessor: "Public.VisitCount" + + }, { + Header: 'Delete', + accessor: 'DeletionURL', + Cell: props => , + style: { textAlign: "center" } + }] + + return ( + + { + return { + onClick: (e, handleOriginal) => { + if (handleOriginal) { + handleOriginal() + } + if (!rowInfo) { + return + } + if (column.id === "DeletionURL") { + return + } + this.onRowClick(rowInfo.row.ID) + } + } + }} /> + + ) + } +} diff --git a/web/src/index.js b/web/src/index.js index 0543a4dc..e1bd35d8 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -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' @@ -186,6 +187,7 @@ export default class BaseComponent extends Component { + diff --git a/web/src/util/util.js b/web/src/util/util.js index 0d4cc147..cd995dfd 100644 --- a/web/src/util/util.js +++ b/web/src/util/util.js @@ -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")) + } } From f8de2ea97e374e47ecf9e8f85fac44c0b5adb951 Mon Sep 17 00:00:00 2001 From: Jeff Billimek Date: Tue, 14 Aug 2018 08:43:20 -0400 Subject: [PATCH 3/4] remove unecessary comments --- internal/stores/redis/redis.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/stores/redis/redis.go b/internal/stores/redis/redis.go index 90516590..0c6c9b44 100644 --- a/internal/stores/redis/redis.go +++ b/internal/stores/redis/redis.go @@ -303,12 +303,8 @@ func (r *Store) GetAllUserEntries() (map[string]shared.Entry, error) { users := r.c.Keys(userToEntriesPrefix + "*") for _, v := range users.Val() { logrus.Debugf("got userEntry: %s", v) - // user, err := r.GetEntryByID(string(v)) - key := v result := r.c.SMembers(key) - // result := r.c.Keys(entryPathPrefix + user) - if result.Err() != nil { msg := fmt.Sprintf("Could not fetch set of entries for user '%s': %v", key, result.Err()) logrus.Errorf(msg) From 81f111da6d18ddee7d9fc79a9c373411476254d5 Mon Sep 17 00:00:00 2001 From: Jeff Billimek Date: Tue, 14 Aug 2018 08:43:56 -0400 Subject: [PATCH 4/4] add-in displayURL support to new admin.js file and address codacy issue --- web/src/Admin/Admin.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/src/Admin/Admin.js b/web/src/Admin/Admin.js index 2ddab119..410b4dcc 100644 --- a/web/src/Admin/Admin.js +++ b/web/src/Admin/Admin.js @@ -8,19 +8,25 @@ import util from '../util/util' export default class AllEntriesComponent extends Component { state = { - allEntries: [] + 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) { - allEntries[key].ID = key; - parsed.push(allEntries[key]); + if ({}.hasOwnProperty.call(allEntries, key)) { + allEntries[key].ID = key; + parsed.push(allEntries[key]); + } } this.setState({ allEntries: parsed }) }) @@ -47,7 +53,7 @@ export default class AllEntriesComponent extends Component { }, { Header: 'Short URL', accessor: "ID", - Cell: props => `${window.location.origin}/${props.value}` + Cell: props => `${this.state.displayURL}/${props.value}` }, { Header: 'Visitor count', accessor: "Public.VisitCount"