Browse Source

- Added schema generation for the configuration

- added example configuration
- changed ID Length to uint instead of int
- Added ShareX configuration generation in the frontend
dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
3f1f231920
  1. 17
      README.md
  2. 19
      build/schema.go
  3. 50
      build/schema.json
  4. 18
      config.dist.json
  5. 3
      config/config.go
  6. 3
      static/package.json
  7. 39
      static/src/ShareX/ShareX.js
  8. 6
      static/src/index.js
  9. 49
      static/yarn.lock
  10. 2
      store/store.go
  11. 2
      store/util.go

17
README.md

@ -7,7 +7,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](http://opensource.org/licenses/MIT) [![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) [![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 - URL Shortening
- Visitor Counting - Visitor Counting
@ -30,11 +30,12 @@ go get -v ./... # Fetch dependencies
go build # Build executable go build # Build executable
./golang-url-shortener # Run it ./golang-url-shortener # Run it
``` ```
### Docker Compose ### Docker Compose
Only execute the [docker-compose.yml](docker-compose.yml) and adjust the environment variables to your needs. 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`: 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 ### General
There is a mechanism integrated, that you can call the `POST` endpoints with the following techniques: There is a mechanism integrated, that you can call the `POST` endpoints with the following techniques:
- application/json - application/json
- application/x-www-form-urlencoded - application/x-www-form-urlencoded
- multipart/form-data - 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 ```bash
curl -X POST -H 'Content-Type: application/json' -d '{"URL":"https://www.google.de/"}' http://127.0.0.1:8080/api/v1/create curl -X POST -H 'Content-Type: application/json' -d '{"URL":"https://www.google.de/"}' http://127.0.0.1:8080/api/v1/create
``` ```
```json ```json
{ {
"URL": "http://127.0.0.1:8080/dgUV", "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 #### Info
```bash ```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 ```json
{ {
"URL": "https://google.com/", "URL": "https://google.com/",
@ -110,7 +114,7 @@ $ curl -X POST -H 'Content-Type: application/json' -d '{"ID":"dgUV"}' http://127
} }
``` ```
### HTTP Endpoints: ### HTTP Endpoints
#### `/api/v1/create` POST #### `/api/v1/create` POST
@ -119,12 +123,13 @@ Create is the handler for creating entries, you need to provide only an `URL`. T
#### `/api/v1/info` POST #### `/api/v1/info` POST
This handler returns the information about an entry. This includes: This handler returns the information about an entry. This includes:
- Created At - Created At
- Last Visit - Last Visit
- Visitor counter - Visitor counter
For that you need to send a field `ID` to the backend. 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. Just only because I want to extend my current self hosted URL shorter and learn about new techniques like Go unit testing and react.

19
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)
}
}

50
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"
}
}
}
}
}

18
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": ""
}
}
}
}

3
config/config.go

@ -12,6 +12,7 @@ import (
// Configuration holds all the needed parameters use // Configuration holds all the needed parameters use
// the URL Shortener // the URL Shortener
type Configuration struct { type Configuration struct {
Schema string `json:"$schema"`
Store Store Store Store
Handlers Handlers Handlers Handlers
} }
@ -19,7 +20,7 @@ type Configuration struct {
// Store contains the needed fields for the Store package // Store contains the needed fields for the Store package
type Store struct { type Store struct {
DBPath string DBPath string
ShortedIDLength int ShortedIDLength uint
} }
// Handlers contains the needed fields for the Handlers package // Handlers contains the needed fields for the Handlers package

3
static/package.json

@ -9,8 +9,11 @@
} }
}, },
"dependencies": { "dependencies": {
"highlight.js": "^9.12.0",
"react": "^16.0.0", "react": "^16.0.0",
"react-clipboard.js": "^1.1.2",
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-fast-highlight": "^2.2.0",
"react-router": "^4.2.0", "react-router": "^4.2.0",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-scripts": "1.0.17", "react-scripts": "1.0.17",

39
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 (
<Container id='rootContainer' >
<div>ShareX</div>
<Highlight languages={['json']}>
{config}
</Highlight>
<ClipboardButton data-clipboard-text={config} className='ui button'>
Copy the configuration to the clipboard
</ClipboardButton>
</Container>
)
}
};

6
static/src/index.js

@ -7,6 +7,7 @@ import 'semantic-ui-css/semantic.min.css';
import About from './About/About' import About from './About/About'
import Home from './Home/Home' import Home from './Home/Home'
import ShareX from './ShareX/ShareX'
export default class BaseComponent extends Component { export default class BaseComponent extends Component {
state = { state = {
@ -20,7 +21,6 @@ export default class BaseComponent extends Component {
this.setState({ open: true }) this.setState({ open: true })
} }
handleItemClick = (e, { name }) => this.setState({ activeItem: name }) handleItemClick = (e, { name }) => this.setState({ activeItem: name })
componentWillMount() { componentWillMount() {
@ -105,12 +105,16 @@ export default class BaseComponent extends Component {
<Menu.Item name='about' active={activeItem === 'about'} onClick={this.handleItemClick} as={Link} to="/about"> <Menu.Item name='about' active={activeItem === 'about'} onClick={this.handleItemClick} as={Link} to="/about">
About About
</Menu.Item> </Menu.Item>
<Menu.Item name='ShareX' active={activeItem === 'ShareX'} onClick={this.handleItemClick} as={Link} to="/sharex">
ShareX
</Menu.Item>
<Menu.Menu position='right'> <Menu.Menu position='right'>
<Menu.Item onClick={this.handleLogout}>Logout</Menu.Item> <Menu.Item onClick={this.handleLogout}>Logout</Menu.Item>
</Menu.Menu> </Menu.Menu>
</Menu> </Menu>
<Route exact path="/" component={Home} /> <Route exact path="/" component={Home} />
<Route path="/about" component={About} /> <Route path="/about" component={About} />
<Route path="/ShareX" component={ShareX} />
</Container> </Container>
</HashRouter> </HashRouter>
) )

49
static/yarn.lock

@ -1303,7 +1303,7 @@ clap@^1.0.9:
dependencies: dependencies:
chalk "^1.1.3" chalk "^1.1.3"
classnames@^2.2.5: classnames@^2.2.3, classnames@^2.2.5:
version "2.2.5" version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" 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" version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 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: cliui@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" 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" version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 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: delegates@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -2741,6 +2753,12 @@ globby@^6.1.0:
pify "^2.0.0" pify "^2.0.0"
pinkie-promise "^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: got@^5.0.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
@ -2857,6 +2875,10 @@ he@1.1.x:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 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: history@^4.7.2:
version "4.7.2" version "4.7.2"
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
@ -4910,7 +4932,7 @@ promise@^7.1.1:
dependencies: dependencies:
asap "~2.0.3" 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" version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies: dependencies:
@ -5027,6 +5049,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" 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: react-dev-utils@^4.2.1:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.2.1.tgz#9f2763e7bafa1a1b9c52254d2a479deec280f111" 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" version "3.0.0"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655" 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: react-router-dom@^4.2.2:
version "4.2.2" version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" 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" version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" 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: selfsigned@^1.9.1:
version "1.10.1" version "1.10.1"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52"
@ -5974,6 +6015,10 @@ timers-browserify@^2.0.2:
dependencies: dependencies:
setimmediate "^1.0.4" 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: tmp@^0.0.33:
version "0.0.33" version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"

2
store/store.go

@ -15,7 +15,7 @@ import (
type Store struct { type Store struct {
db *bolt.DB db *bolt.DB
bucketName []byte bucketName []byte
idLength int idLength uint
} }
// 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

2
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 // generateRandomString generates a random string with an predefined length
func generateRandomString(length int) string { func generateRandomString(length uint) string {
letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, length) b := make([]rune, length)
for i := range b { for i := range b {

Loading…
Cancel
Save