From 3f1f23192028befdbc38f3acdcdf53d4616e72da Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 6 Nov 2017 22:02:22 +0100 Subject: [PATCH] - Added schema generation for the configuration - added example configuration - changed ID Length to uint instead of int - Added ShareX configuration generation in the frontend --- README.md | 21 ++++++++++------ build/schema.go | 19 ++++++++++++++ build/schema.json | 50 +++++++++++++++++++++++++++++++++++++ config.dist.json | 18 +++++++++++++ config/config.go | 3 ++- static/package.json | 3 +++ static/src/ShareX/ShareX.js | 39 +++++++++++++++++++++++++++++ static/src/index.js | 6 ++++- static/yarn.lock | 49 ++++++++++++++++++++++++++++++++++-- store/store.go | 2 +- store/util.go | 2 +- 11 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 build/schema.go create mode 100644 build/schema.json create mode 100644 config.dist.json create mode 100644 static/src/ShareX/ShareX.js diff --git a/README.md b/README.md index f48d9b7..51f1278 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](http://opensource.org/licenses/MIT) [![Waffle.io - Columns and their card count](https://badge.waffle.io/maxibanki/golang-url-shortener.png?columns=all)](https://waffle.io/maxibanki/golang-url-shortener?utm_source=badge) -## Main Features: +## Main Features - URL Shortening - Visitor Counting -- URL deletion +- URL deletion - Authorization System - High performance database with [bolt](https://github.com/boltdb/bolt) - [ShareX](https://github.com/ShareX/ShareX) integration @@ -30,11 +30,12 @@ go get -v ./... # Fetch dependencies go build # Build executable ./golang-url-shortener # Run it ``` + ### Docker Compose Only execute the [docker-compose.yml](docker-compose.yml) and adjust the environment variables to your needs. -### Configuration: +### Configuration The configuration is a yaml based file of key value pairs. It is located in the installation folder and is called `config.yml`: @@ -54,11 +55,12 @@ The configuration is a yaml based file of key value pairs. It is located in the } ``` -## Clients: +## Clients ### General There is a mechanism integrated, that you can call the `POST` endpoints with the following techniques: + - application/json - application/x-www-form-urlencoded - multipart/form-data @@ -90,6 +92,7 @@ After you've done this, you need to set it as your standard URL Shortener. For t ```bash curl -X POST -H 'Content-Type: application/json' -d '{"URL":"https://www.google.de/"}' http://127.0.0.1:8080/api/v1/create ``` + ```json { "URL": "http://127.0.0.1:8080/dgUV", @@ -99,8 +102,9 @@ curl -X POST -H 'Content-Type: application/json' -d '{"URL":"https://www.google. #### Info ```bash -$ curl -X POST -H 'Content-Type: application/json' -d '{"ID":"dgUV"}' http://127.0.0.1:8080/api/v1/info +curl -X POST -H 'Content-Type: application/json' -d '{"ID":"dgUV"}' http://127.0.0.1:8080/api/v1/info ``` + ```json { "URL": "https://google.com/", @@ -110,21 +114,22 @@ $ curl -X POST -H 'Content-Type: application/json' -d '{"ID":"dgUV"}' http://127 } ``` -### HTTP Endpoints: +### HTTP Endpoints #### `/api/v1/create` POST Create is the handler for creating entries, you need to provide only an `URL`. The response will always be JSON encoded and contain an URL with the short link. -#### `/api/v1/info` POST +#### `/api/v1/info` POST This handler returns the information about an entry. This includes: + - Created At - Last Visit - Visitor counter For that you need to send a field `ID` to the backend. -## Why did you built this? +## Why did you built this Just only because I want to extend my current self hosted URL shorter and learn about new techniques like Go unit testing and react. \ No newline at end of file diff --git a/build/schema.go b/build/schema.go new file mode 100644 index 0000000..9beac86 --- /dev/null +++ b/build/schema.go @@ -0,0 +1,19 @@ +package main + +import ( + "flag" + "io/ioutil" + "log" + + "github.com/maxibanki/golang-url-shortener/config" + "github.com/urakozz/go-json-schema-generator" +) + +func main() { + schemaPath := flag.String("path", "schema.json", "location of the converted schema") + flag.Parse() + schema := generator.Generate(&config.Configuration{}) + if err := ioutil.WriteFile(*schemaPath, []byte(schema), 644); err != nil { + log.Fatalf("could not write schema: %v", err) + } +} diff --git a/build/schema.json b/build/schema.json new file mode 100644 index 0000000..103891c --- /dev/null +++ b/build/schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "Handlers": { + "type": "object", + "properties": { + "BaseURL": { + "type": "string" + }, + "EnableGinDebugMode": { + "type": "boolean" + }, + "ListenAddr": { + "type": "string" + }, + "OAuth": { + "type": "object", + "properties": { + "Google": { + "type": "object", + "properties": { + "ClientID": { + "type": "string" + }, + "ClientSecret": { + "type": "string" + } + } + } + } + }, + "Secret": { + "type": "string" + } + } + }, + "Store": { + "type": "object", + "properties": { + "DBPath": { + "type": "string" + }, + "ShortedIDLength": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/config.dist.json b/config.dist.json new file mode 100644 index 0000000..7b23012 --- /dev/null +++ b/config.dist.json @@ -0,0 +1,18 @@ +{ + "$schema": "./build/schema.json", + "Store": { + "DBPath": "main.db", + "ShortedIDLength": 4 + }, + "Handlers": { + "ListenAddr": ":8080", + "BaseURL": "http://localhost:3000", + "EnableGinDebugMode": false, + "OAuth": { + "Google": { + "ClientID": "", + "ClientSecret": "" + } + } + } +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index 4bde030..0ed5b50 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ import ( // Configuration holds all the needed parameters use // the URL Shortener type Configuration struct { + Schema string `json:"$schema"` Store Store Handlers Handlers } @@ -19,7 +20,7 @@ type Configuration struct { // Store contains the needed fields for the Store package type Store struct { DBPath string - ShortedIDLength int + ShortedIDLength uint } // Handlers contains the needed fields for the Handlers package diff --git a/static/package.json b/static/package.json index 0c45543..4790b4a 100644 --- a/static/package.json +++ b/static/package.json @@ -9,8 +9,11 @@ } }, "dependencies": { + "highlight.js": "^9.12.0", "react": "^16.0.0", + "react-clipboard.js": "^1.1.2", "react-dom": "^16.0.0", + "react-fast-highlight": "^2.2.0", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-scripts": "1.0.17", diff --git a/static/src/ShareX/ShareX.js b/static/src/ShareX/ShareX.js new file mode 100644 index 0000000..cc37a39 --- /dev/null +++ b/static/src/ShareX/ShareX.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react' +import { Container } from 'semantic-ui-react' +import { Highlight } from 'react-fast-highlight'; +import ClipboardButton from 'react-clipboard.js'; +import 'highlight.js/styles/github.css' + +export default class ShareXComponent extends Component { + state = { + config: JSON.stringify({ + Name: "Golang URL Shortener", + DestinationType: "URLShortener", + RequestType: "POST", + RequestURL: window.location.origin + "/api/v1/protected/create", + Arguments: { + URL: "$input$" + }, + Headers: { + Authorization: window.localStorage.getItem('token') + }, + ResponseType: "Text", + URL: "$json:URL$" + }, null, 4) + } + + render() { + const { config } = this.state + return ( + +
ShareX
+ + {config} + + + Copy the configuration to the clipboard + +
+ ) + } +}; diff --git a/static/src/index.js b/static/src/index.js index bc016d6..273875f 100644 --- a/static/src/index.js +++ b/static/src/index.js @@ -7,6 +7,7 @@ import 'semantic-ui-css/semantic.min.css'; import About from './About/About' import Home from './Home/Home' +import ShareX from './ShareX/ShareX' export default class BaseComponent extends Component { state = { @@ -20,7 +21,6 @@ export default class BaseComponent extends Component { this.setState({ open: true }) } - handleItemClick = (e, { name }) => this.setState({ activeItem: name }) componentWillMount() { @@ -105,12 +105,16 @@ export default class BaseComponent extends Component { About + + ShareX + Logout + ) diff --git a/static/yarn.lock b/static/yarn.lock index 6debfff..3ea38db 100644 --- a/static/yarn.lock +++ b/static/yarn.lock @@ -1303,7 +1303,7 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" -classnames@^2.2.5: +classnames@^2.2.3, classnames@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" @@ -1327,6 +1327,14 @@ cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" +clipboard@^1.6.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b" + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -1787,6 +1795,10 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" +delegate@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.3.tgz#9a8251a777d7025faa55737bc3b071742127a9fd" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -2741,6 +2753,12 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + dependencies: + delegate "^3.1.2" + got@^5.0.0: version "5.7.1" resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" @@ -2857,6 +2875,10 @@ he@1.1.x: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +highlight.js@^9.0.0, highlight.js@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -4910,7 +4932,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0: +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -5027,6 +5049,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-clipboard.js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.2.tgz#96c92c928d09873c14287fd5c75f395f1f911490" + dependencies: + clipboard "^1.6.1" + prop-types "^15.5.0" + react-dev-utils@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.2.1.tgz#9f2763e7bafa1a1b9c52254d2a479deec280f111" @@ -5063,6 +5092,14 @@ react-error-overlay@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655" +react-fast-highlight@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-fast-highlight/-/react-fast-highlight-2.2.0.tgz#afdbdfab9f3b8bd573b3e2d22b568d92cb93b42a" + dependencies: + classnames "^2.2.3" + highlight.js "^9.0.0" + prop-types "^15.5.6" + react-router-dom@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" @@ -5482,6 +5519,10 @@ select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + selfsigned@^1.9.1: version "1.10.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" @@ -5974,6 +6015,10 @@ timers-browserify@^2.0.2: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" diff --git a/store/store.go b/store/store.go index 8f343d4..7d8f7ca 100644 --- a/store/store.go +++ b/store/store.go @@ -15,7 +15,7 @@ import ( type Store struct { db *bolt.DB bucketName []byte - idLength int + idLength uint } // Entry is the data set which is stored in the DB as JSON diff --git a/store/util.go b/store/util.go index c974f40..8e00af8 100644 --- a/store/util.go +++ b/store/util.go @@ -57,7 +57,7 @@ func (s *Store) createEntry(URL, remoteAddr string) (string, error) { } // generateRandomString generates a random string with an predefined length -func generateRandomString(length int) string { +func generateRandomString(length uint) string { letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, length) for i := range b {