Skip to content

Commit

Permalink
👾
Browse files Browse the repository at this point in the history
  • Loading branch information
poeti8 committed Feb 13, 2018
0 parents commit 6af6948
Show file tree
Hide file tree
Showing 134 changed files with 14,933 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["next/babel", "env"],
"plugins": [["styled-components", { "ssr": true, "displayName": true, "preprocess": false }]]
}
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEST=test
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.next/
flow-typed/
node_modules/
34 changes: 34 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"extends": [
"airbnb",
"prettier",
"prettier/react"
],
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true
},
"rules": {
"react/jsx-filename-extension": [
1,
{
"extensions": [
".js",
".jsx"
]
}
],
"prettier/prettier": [
"error",
{
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100
}
]
},
"plugins": [
"prettier"
]
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.vscode/
client/.next/
node_modules/
client/config.js
server/config.js
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: node_js

node_js:
- "8"

before_install:
- cp ./server/config.example.js ./server/config.js
- cp ./client/config.example.js ./client/config.js

script:
- npm run lint:nofix
- npm run build
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 The Devs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<a href="https://kutt.it" title="kutt.it"><img src="https://camo.githubusercontent.com/073e709d02d3cf6ee5439ee6ce0bb0895f9f3733/687474703a2f2f6f6936372e74696e797069632e636f6d2f3636797a346f2e6a7067" alt="Kutt.it"></a>

# Kutt.it

**Kutt** is a modern URL shortener which lets you set custom domains for your shortened URLs, manage your links and view the click rate statistics.

*Contributions and bug reports are welcome.*

[https://kutt.it](https://kutt.it)

[![Build Status](https://travis-ci.org/thedevs-network/kutt.svg?branch=develop)](https://travis-ci.org/thedevs-network/kutt)
[![Contributions](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/thedevs-network/kutt/#contributing)
[![GitHub license](https://img.shields.io/github/license/thedevs-network/kutt.svg)](https://github.com/thedevs-network/kutt/blob/develop/LICENSE)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/thedevs-network/kutt/.svg?style=social)](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fthedevs-network%2Fkutt%2F)

## Table of Contents
* [Key Features](#key-features)
* [Stack](#stack)
* [Setup](#setup)
* [API](#api)
* [Contributing](#contributing)

## Key Features
* Free and open source.
* Setting custom domain.
* Using custom URLs for shortened links
* Setting password for links.
* Private statistics for shortened URLs.
* View and manage your links.
* Provided API.

## Stack
* Node (Web server)
* Express (Web server framework)
* Passport (Authentication)
* React (UI library)
* Next (Universal/server-side rendered React)
* Redux (State management)
* styled-components (CSS styling solution library)
* Recharts (Chart library)
* Neo4j (Graph database)

## Setup
You need to have [Node.js](https://nodejs.org/) and [Neo4j](https://neo4j.com/) installed on your system.

1. Clone this repository on [downlaod zip](https://github.com/thedevs-network/kutt/archive/master.zip).
2. Copy `config.example.js` to `config.js` in both server and client folders and fill them properly.
3. Install dependencies: `npm install`.
4. Start Neo4j database.
5. Run for development: `npm run dev`.
6. Run for production: `npm run build` then `npm start`.

## API
In additional to website, you can use these APIs to create, delete and get URLs.

In order to use these APIs you need to generate an API key from settings. Don not ever put this key in the client side of your app or anywhere that is exposed to others.

Include API key as `apikey` in the body of all below requests. Available API URLs with body parameters:

**Get shortened URLs list:**
```
POST /api/url/geturls
```

**Submit a links to be shortened**:
```
POST /api/url/submit
```
Body:
* `target`: Original long URL to be shortened.

**Delete a shortened URL** and **Get stats for a shortened URL:**
```
POST /api/url/deleteurl
POST /api/url/stats
```
Body
* `id`: ID of the shortened URL.
* `domain` (optional): Required if a custom domain is used for short URL.

## Contributing
Pull requests are welcome. You'll probably find lots of improvements to be made.

Open issues for feadback, needed features, reporting bugs or discussing ideas.
30 changes: 30 additions & 0 deletions client/actions/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* Homepage input actions */
export const ADD_URL = 'ADD_URL';
export const UPDATE_URL_LIST = 'UPDATE_URL_LIST';
export const LIST_URLS = 'LIST_URLS';
export const DELETE_URL = 'DELETE_URL';
export const SHORTENER_ERROR = 'SHORTENER_ERROR';
export const SHORTENER_LOADING = 'SHORTENER_LOADING';
export const TABLE_LOADING = 'TABLE_LOADING';

/* Page loading actions */
export const SHOW_PAGE_LOADING = 'SHOW_PAGE_LOADING';
export const HIDE_PAGE_LOADING = 'HIDE_PAGE_LOADING';

/* Login & signup actions */
export const AUTH_USER = 'AUTH_USER';
export const AUTH_RENEW = 'AUTH_RENEW';
export const UNAUTH_USER = 'UNAUTH_USER';
export const SENT_VERIFICATION = 'SENT_VERIFICATION';
export const AUTH_ERROR = 'AUTH_ERROR';
export const LOGIN_LOADING = 'LOGIN_LOADING';
export const SIGNUP_LOADING = 'SIGNUP_LOADING';

/* Settings actions */
export const SET_DOMAIN = 'SET_DOMAIN';
export const SET_APIKEY = 'SET_APIKEY';
export const DELETE_DOMAIN = 'DELETE_DOMAIN';
export const DOMAIN_LOADING = 'DOMAIN_LOADING';
export const API_LOADING = 'API_LOADING';
export const DOMAIN_ERROR = 'DOMAIN_ERROR';
export const SHOW_DOMAIN_INPUT = 'SHOW_DOMAIN_INPUT';
137 changes: 137 additions & 0 deletions client/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import Router from 'next/router';
import axios from 'axios';
import cookie from 'js-cookie';
import decodeJwt from 'jwt-decode';
import * as types from './actionTypes';

/* Homepage input actions */
const addUrl = payload => ({ type: types.ADD_URL, payload });
const listUrls = payload => ({ type: types.LIST_URLS, payload });
const updateUrlList = payload => ({ type: types.UPDATE_URL_LIST, payload });
const deleteUrl = payload => ({ type: types.DELETE_URL, payload });
const showShortenerLoading = () => ({ type: types.SHORTENER_LOADING });
const showTableLoading = () => ({ type: types.TABLE_LOADING });
export const setShortenerFormError = payload => ({ type: types.SHORTENER_ERROR, payload });

export const createShortUrl = params => dispatch => {
dispatch(showShortenerLoading());
return axios
.post('/api/url/submit', params, { headers: { Authorization: cookie.get('token') } })
.then(({ data }) => dispatch(addUrl(data)))
.catch(({ response }) => dispatch(setShortenerFormError(response.data.error)));
};

export const getUrlsList = params => (dispatch, getState) => {
if (params) dispatch(updateUrlList(params));
dispatch(showTableLoading());
return axios
.post('/api/url/geturls', getState().url, { headers: { Authorization: cookie.get('token') } })
.then(({ data }) => dispatch(listUrls(data)));
};

export const deleteShortUrl = params => dispatch => {
dispatch(showTableLoading());
return axios
.post('/api/url/deleteurl', params, { headers: { Authorization: cookie.get('token') } })
.then(() => dispatch(deleteUrl(params.id)))
.catch(({ response }) => dispatch(setShortenerFormError(response.data.error)));
};
/* Page loading actions */
export const showPageLoading = () => ({ type: types.SHOW_PAGE_LOADING });
export const hidePageLoading = () => ({ type: types.HIDE_PAGE_LOADING });

/* Settings actions */
export const setDomain = payload => ({ type: types.SET_DOMAIN, payload });
export const setApiKey = payload => ({ type: types.SET_APIKEY, payload });
const deleteDomain = () => ({ type: types.DELETE_DOMAIN });
const setDomainError = payload => ({ type: types.DOMAIN_ERROR, payload });
const showDomainLoading = () => ({ type: types.DOMAIN_LOADING });
const showApiLoading = () => ({ type: types.API_LOADING });
export const showDomainInput = () => ({ type: types.SHOW_DOMAIN_INPUT });

export const getUserSettings = () => dispatch =>
axios
.post('/api/auth/usersettings', null, { headers: { Authorization: cookie.get('token') } })
.then(({ data }) => {
dispatch(setDomain(data.customDomain));
dispatch(setApiKey(data.apikey));
});

export const setCustomDomain = params => dispatch => {
dispatch(showDomainLoading());
return axios
.post('/api/url/customdomain', params, { headers: { Authorization: cookie.get('token') } })
.then(({ data }) => dispatch(setDomain(data.customDomain)))
.catch(({ response }) => dispatch(setDomainError(response.data.error)));
};

export const deleteCustomDomain = () => dispatch =>
axios
.delete('/api/url/customdomain', { headers: { Authorization: cookie.get('token') } })
.then(() => dispatch(deleteDomain()))
.catch(({ response }) => dispatch(setDomainError(response.data.error)));

export const generateApiKey = () => dispatch => {
dispatch(showApiLoading());
return axios
.post('/api/auth/generateapikey', null, { headers: { Authorization: cookie.get('token') } })
.then(({ data }) => dispatch(setApiKey(data.apikey)));
};

/* Login & signup actions */
export const authUser = payload => ({ type: types.AUTH_USER, payload: decodeJwt(payload).sub });
export const unauthUser = () => ({ type: types.UNAUTH_USER });
export const sentVerification = payload => ({ type: types.SENT_VERIFICATION, payload });
export const showAuthError = payload => ({ type: types.AUTH_ERROR, payload });
export const showLoginLoading = () => ({ type: types.LOGIN_LOADING });
export const showSignupLoading = () => ({ type: types.SIGNUP_LOADING });
export const authRenew = () => ({ type: types.AUTH_RENEW });

export const signupUser = body => dispatch => {
dispatch(showSignupLoading());
return axios
.post('/api/auth/signup', body)
.then(res => {
const { email } = res.data;
dispatch(sentVerification(email));
})
.catch(err => dispatch(showAuthError(err.response.data.error)));
};

export const loginUser = body => dispatch => {
dispatch(showLoginLoading());
return axios
.post('/api/auth/login', body)
.then(res => {
const { token } = res.data;
cookie.set('token', token, { expires: 7 });
dispatch(authRenew());
dispatch(authUser(token));
dispatch(showPageLoading());
Router.push('/');
})
.catch(err => dispatch(showAuthError(err.response.data.error)));
};

export const logoutUser = () => dispatch => {
dispatch(showPageLoading());
cookie.remove('token');
dispatch(unauthUser());
return Router.push('/login');
};

export const renewAuthUser = () => (dispatch, getState) => {
if (getState().auth.renew) return null;
return axios
.post('/api/auth/renew', null, { headers: { Authorization: cookie.get('token') } })
.then(res => {
const { token } = res.data;
cookie.set('token', token, { expires: 7 });
dispatch(authRenew());
dispatch(authUser(token));
})
.catch(() => {
cookie.remove('token');
dispatch(unauthUser());
});
};
Loading

0 comments on commit 6af6948

Please sign in to comment.