Coder Social home page Coder Social logo

amitt001 / pygmy Goto Github PK

View Code? Open in Web Editor NEW
676.0 12.0 130.0 1.79 MB

An open-source, feature rich & extensible url-shortener + analytics written in Python :cookie:

Home Page: https://demo.pygy.co/pygmy

License: MIT License

Python 81.33% CSS 1.33% JavaScript 1.95% HTML 13.69% Shell 1.48% Dockerfile 0.22%
url-shortener python3 python flask django urlshortener pygmy shortener analytics

pygmy's Introduction

pygmy

Pygmy

Coverage Status PyPI - Python Version PyPI license Docker Pulls

Demo Version: https://demo.pygy.co

Link stats(add + to the URL) example: demo.pygy.co/pygmy+

Hackernews Thread: https://news.ycombinator.com/item?id=17690559

Table of Contents

Pygmy or pygy.co is an open-source, extensible & easy-to-use but powerful URL shortener. It's created keeping in mind that it should be easy to host and run your custom URL shortener without much effort. [Open-source Python URL shortener]

The architecture is very loosely coupled which allows custom integrations easily.

The project has 3 major parts

  • The core URL shortening code
  • A REST API on top. Uses Flask framework
  • The UI layer for rendering the UI. It uses the Django framework

Features

  • URL shortener
  • Customized short URL's(ex: pygy.co/pygmy)
  • Support to create auto expiry URL after some time.
  • Secret key protected URL's
  • User Login/Sign up to track shortened URL's and link stats
  • User dashboard
  • Link Analytics(add + to the tiny URL to get link stats)

Technical Info

  • Python 3, Javascript, JQuery, HTML, CSS
  • REST API: Flask
  • Pygmyui: Django(It serves the web user-interface)
  • Supported DBs: PostgreSQL/MySQL/SQLite
  • Others: SQLAlchmey, JWT
  • Docker
  • Docker-compose

Setup

Docker

  1. In terminal run this command: docker pull amit19/pygmy
  2. Then run the container: docker run -it -p 8000:8000 amit19/pygmy
  3. Open http://localhost:8000 in your browser

Manual(from source)

  1. Clone git clone https://github.com/amitt001/pygmy.git & cd pygmy
  2. (Optional) Install virtualenv (optional but recommended)
    • virtualenv -p python3 env
    • source env/bin/activate
  3. Install dependencies: pip3 install -r requirements.txt (if you are using MySQL or PostgreSQL check DB setup section)
  4. python run.py (It runs Flask and Django servers using gunicorn)
  5. Visit 127.0.0.1:8000 to use the app
  6. Logs can be viewed at pygmy/data/pygmy.log

Note:

  • This module only supports Python 3. Make sure pip and virtualenv are both python 3 based versions.(To install Python 3 on Mac: http://docs.python-guide.org/en/latest/starting/install3/osx/)
  • The project has two config files:
    • pygmy.cfg: pygmy/config/pygmy.cfg rest API and pygmy core settings file
    • settings.py: pygmyui/pygmyui/settings.py Django settings file
  • SQLite is default DB, if you are using PostgreSQL or MySQL with this project, make sure they are installed into the system.
  • You can run pygmy shell also. Present in the root directory. To run the program on the terminal: python shell
  • By default, DEBUG is set to True in pygmyui/pygmyui/settings.py file, set it to False in production.

DB Setup:

By default, Pygmy uses SQLite but any of the DB, SQLite, MySQL or PostgreSQL, can be used. Configs is present at pygmy/config/pygmy.cfg.

Use DB specific instruction below. Make sure to check and modify values in pygmy.cfg file according to your DB setup.

Use MySQL

  1. Install pymysql: pip install pymysql

  2. Check correct port: mysqladmin variables | grep port

  3. Change below line in pygmy/core/pygmy.cfg file:

[database]
engine: mysql
url: {engine}://{user}:{password}@{host}:{port}/{db_name}
user: root
password: root
host: 127.0.0.1
port: 3306
db_name: pygmy
  1. Enter MySQL URL CREATE DATABASE pygmy;

Note: It's better to use Mysql with version > 5.6.5 to use the default value of CURRENT_TIMESTAMP for DATETIME.

Use Postgresql

  1. Change below line in pygmy/core/pygmy.cfg file:
[database]
engine: postgresql
url: {engine}://{user}:{password}@{host}:{port}/{db_name}
user: root
password: root
host: 127.0.0.1
port: 5432
db_name: pygmy

Use SQLite

SQLite is natively supported in Python

  1. Update sqlite:////var/lib/pygmy/pygmy.db file
[database]
engine: sqlite3
sqlite_data_dir: data
sqlite_db_file_name: pygmy.db

Docker

Docker image name: amit19/pygmy. Docker image can be built by running docker build -t amit19/pygmy . command. Both the Dockerfile and docker-compose file are present at the root of the project. To use docker-compose you need to pass DB credentials in the docker-compose file.

Using Pygmy API

Create User

curl -XPOST http://127.0.0.1:9119/api/user/1 -H 'Content-Type: application/json' -d '{
"email": "[email protected]",
"f_name": "Amit",
"l_name": "Tripathi",
"password": "a_safe_one"
}'

Shell Usage

Open shell using ./shell. Available context in shell are: pygmy, Config, DB, etc. See all context by using pygmy_context.

Shorten a link:

In [1]: shorten('http://iamit.xyz')
Out[1]:
{'created_at': '15 Nov, 2017 17:33:42',
 'description': None,
 'expire_after': None,
 'hits_counter': 0,
 'id': 'http://0.0.0.0:9119/api/link/5',
 'is_custom': False,
 'is_disabled': False,
 'is_protected': False,
 'long_url': 'http://iamit.xyz',
 'owner': None,
 'secret_key': '',
 'short_code': 'f',
 'short_url': 'http://pygy.co/f',
 'updated_at': '2017-11-15T17:33:42.772520+00:00'}

In [2]: shorten('http://iamit.xyz', request=1)
Out[2]: <pygmy.model.link.Link at 0x105ca1b70>

In [3]: unshorten('f')
Out[3]:
{'created_at': '15 Nov, 2017 17:33:42',
 'description': None,
 'expire_after': None,
 'hits_counter': 0,
 'id': 'http://0.0.0.0:9119/api/link/5',
 'is_custom': False,
 'is_disabled': False,
 'is_protected': False,
 'long_url': 'http://iamit.xyz',
 'owner': None,
 'secret_key': '',
 'short_code': 'f',
 'short_url': 'http://pygy.co/f',
 'updated_at': '2017-11-15T17:33:42.772520+00:00'}

In [4]: link_stats('f')
Out[4]:
{'country_stats': 0,
 'created_at': datetime.datetime(2017, 11, 15, 17, 33, 42, 772520),
 'long_url': 'http://iamit.xyz',
 'referrer': 0,
 'short_code': 'f',
 'time_series_base': None,
 'time_stats': 0,
 'total_hits': 0}

In [5]: # check the available context of the shell
In [6]: pygmy_context

In [7]: # Create custom short URL

In [8]: shorten('http://iamit.xyz', short_code='amit')
Out[8]:
{'long_url': 'http://iamit.xyz',
 'short_code': 'amit',
 'short_url': 'http://pygy.co/amit'}

In [9]: shorten?
Signature: shorten(long_url, short_code=None, expire_after=None, description=None, secret_key=None, owner=None, request=None)
Docstring:
    Helper class that has been delegated the task of inserting the
    passed url in DB, base 62 encoding from DB id and return the short
    URL value.

Q. How Link Stats Are Generated?

For getting geo location stats from IP maxminds' GeoLite2-Country.mmd database is used. It's in pygmy/app directory.

Q. How Pygmy Auth Token Works?

It uses JWT. When user logs in using username and password two tokens are generated, refresh token and auth token. Auth token is used for authentication with the Pygmy API. The refresh token can only be used to generate a new auth token. Auth token has a very short TTL but refresh token has a longer TTL. After 30 minutes. When a request comes with the old auth token and a new token is generated from the refresh token API. User passwords are encrypted by bcrypt hash algorithm.

Development

If you find any bug, have a question or a general feature request. Open an issue on the 'Issue' page.

To contribute to the project:

  1. Clone the repo and make changes
  2. Build the code: docker build pygmy
  3. Test the changer by running: docker run -it -p 8000:8000 pygmy
  4. The website will be available at http://127.0.0.1:8000/

Run tests:

  1. Install pytest (if not already installed): pip install pytest
  2. In root directory run command: py.test

Run tests with coverage report

  1. Install coverage pip install coverage
  2. Run command: coverage run --omit="*/templates*,*/venv*,*/tests*" -m py.test
  3. See coverage report(Coverage numbers are low as the coverage for integration tests is not generated): coverage report

Sponsorship

The demo version of this website is made possible due to the generous sponsorship of DigitalOcean

License

MIT License

Copyright (c) 2022 Amit Tripathi(https://twitter.com/amitt019)

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.

Read License Terms

pygmy's People

Contributors

amitt001 avatar dependabot[bot] avatar hamidaskarov avatar hu1-li avatar pangoraw avatar svilgelm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pygmy's Issues

400: Malicious Attacks

I've used this for quite some time, fully understanding it could go away at any moment, and now that it has I miss it dearly; any chance this is going to get back up and running?

Error message on site:

400
URL shortening temporarily disabled due to malicious attacks. Contact in case of any questions/issues

Link with an expiry time, shown as expired = False on dashboard even after expiry time

The pygmy application offers an option to create links that expire after some specific time. This works by attaching an expiry time(in minutes), which is a positive integer, to the link record in DB.

The expiry time of an URL is calculated by url creation time + expiry time and if it is less than the current time, the short link is considered expired and can't be used anymore. If a user is logged in, the dashboard will show this URL as either expired = True or False, depending on weather url was opened after it's expiry time or not. This is where the bug lies.

The reason why the dashboard doesn't have real-time knowledge about the state of URL is, the expiry value in DB is set if a call comes for URL after it has expired. There is no background job for marking the URL as expired.

How this issue can be fixed?
This can be fixed by checking the expiry of URL on the application level when building the response data for the dashboard. URL's can be marked as expired at this point and any subsequent call to dashboard doesn't need to make any more updates.

Show country with country code on URL stats page

Currently, the stats page just shows country code like IN, US, ZA etc. It's not always intuitive to know the country from its code. Example: https://pygy.co/pygmy+

The stats page should show Country (code). Example: India(IN)
One way to do it would be to keep country iso code and country name mapping in the code somewhere.

Production config files example

Hi there

I'm testing pygmy in a homemade Helm chart. A bit in trouble with Django behing unhappy with CSRF. Would you mind providing/documenting config files examples, both pygmy's and pygmyui's ?

Best regards,

How to migrate DB?

Hi,

It seems that pygmy doesn't migrate db automatically. Please tell me how to migrate DB.

Feature request - randomised ids

Hi there,

Love your project, just one feature request if possible - can you optionally make config line where we will be able to configure random id with the conversion it to base62 (so basically random and not starting from id=1, but already from id=100,000,000)

The main reason is to make same-looking final url with 4-5 symbols and random order as:
/yuwUi
/ijjeJ
/JhjdI

Feature Request - Simplified Flask Implementation

This is very impressive!

This is the exact tool I've been looking for with the two features I wanted most: link click stats & link expiration.

Is there anyway someone would be willing to convert this repo into a simplified flask-implementation? My entire setup for a project I'm working on is built on the flask framework and I was looking to have a custom link shortener with

  1. The option to have a link expire after a set amount of time (say you send a link to a customer for an invoice, they would only have access to the link for 72 hours or so at which point the invoice is no longer valid).
  2. The option to just track link clicks. I'm not worried about location really.
  • This feature would be great for the above mentioned invoice situation to see if the client has opened the invoice
  • It could also be beneficial for email campaigns to know which customers showed interest in particular services or deals. That would allow for potential Lead generation to more accurately target potential upsell opportunities.

About the `next_short_code` function

Here is the simple test result, about function next_short_code

  • Order by Link.ID desc
mysql> select id, long_url, short_code, created_at from link where id between 15581 and 15585 order by id desc;
+-------+----------------------------+------------+---------------------+
| id    | long_url                   | short_code | created_at          |
+-------+----------------------------+------------+---------------------+
| 15585 | http://test.com/a/b/c/5582 | l95        | 2018-05-16 02:11:59 |
| 15584 | http://test.com/a/b/c/5581 | l94        | 2018-05-16 02:11:59 |
| 15583 | http://test.com/a/b/c/5580 | l93        | 2018-05-16 02:11:59 |
| 15582 | http://test.com/a/b/c/5579 | l92        | 2018-05-16 02:11:58 |
| 15581 | http://test.com/a/b/c/5578 | l91        | 2018-05-16 02:11:58 |
+-------+----------------------------+------------+---------------------+
  • Order by Link.created_at desc
mysql> select id, long_url, short_code, created_at from link where id between 15581 and 15585 order by created_at desc;
+-------+----------------------------+------------+---------------------+
| id    | long_url                   | short_code | created_at          |
+-------+----------------------------+------------+---------------------+
| 15583 | http://test.com/a/b/c/5580 | l93        | 2018-05-16 02:11:59 |
| 15584 | http://test.com/a/b/c/5581 | l94        | 2018-05-16 02:11:59 |
| 15585 | http://test.com/a/b/c/5582 | l95        | 2018-05-16 02:11:59 |
| 15581 | http://test.com/a/b/c/5578 | l91        | 2018-05-16 02:11:58 |
| 15582 | http://test.com/a/b/c/5579 | l92        | 2018-05-16 02:11:58 |
+-------+----------------------------+------------+---------------------+

As you can see, order by link.created_at desc is not always corect.

And If using link.created_at desc will get the incorrect answer of base_str, which will trapped into the while cycle.

here is the log of simple call next_short_code, which iter from h2k to h2x.

2018-05-16 02:05:12,407 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.is_custom IS false AND link.short_code IS NOT NULL AND link.short_code != %(short_code_1)s ORDER BY link.created_at DESC
 LIMIT %(param_1)s
2018-05-16 02:05:12,407 INFO sqlalchemy.engine.base.Engine {'short_code_1': '', 'param_1': 1}
2018-05-16 02:05:12,417 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,418 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2k'}
2018-05-16 02:05:12,419 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,419 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2k'}
2018-05-16 02:05:12,423 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,423 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2l'}
2018-05-16 02:05:12,424 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,424 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2l'}
2018-05-16 02:05:12,429 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,429 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2m'}
2018-05-16 02:05:12,430 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,430 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2m'}
2018-05-16 02:05:12,434 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,434 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2n'}
2018-05-16 02:05:12,435 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,435 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2n'}
2018-05-16 02:05:12,439 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,439 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2o'}
2018-05-16 02:05:12,440 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,440 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2o'}
2018-05-16 02:05:12,444 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,445 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2p'}
2018-05-16 02:05:12,446 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,446 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2p'}
2018-05-16 02:05:12,450 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,450 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2q'}
2018-05-16 02:05:12,451 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,451 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2q'}
2018-05-16 02:05:12,455 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,455 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2r'}
2018-05-16 02:05:12,457 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,457 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2r'}
2018-05-16 02:05:12,461 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,461 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2s'}
2018-05-16 02:05:12,462 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,462 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2s'}
2018-05-16 02:05:12,466 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,466 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2t'}
2018-05-16 02:05:12,467 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,467 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2t'}
2018-05-16 02:05:12,471 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,471 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2u'}
2018-05-16 02:05:12,473 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,473 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2u'}
2018-05-16 02:05:12,477 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,477 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2v'}
2018-05-16 02:05:12,478 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,478 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2v'}
2018-05-16 02:05:12,482 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,482 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2w'}
2018-05-16 02:05:12,483 INFO sqlalchemy.engine.base.Engine SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s
2018-05-16 02:05:12,483 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2w'}
2018-05-16 02:05:12,488 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM (SELECT link.id AS link_id, link.long_url AS link_long_url, link.protocol AS link_protocol, link.domain AS link_domain, link.long_url_hash AS link_long_url_hash, link.short_code AS link_short_code, link.description AS link_description, link.owner AS link_owner, link.secret_key AS link_secret_key, link.expire_after AS link_expire_after, link.is_default AS link_is_default, link.is_protected AS link_is_protected, link.is_disabled AS link_is_disabled, link.is_custom AS link_is_custom, link.created_at AS link_created_at, link.updated_at AS link_updated_at
FROM link
WHERE link.short_code = %(short_code_1)s) AS anon_1
2018-05-16 02:05:12,488 INFO sqlalchemy.engine.base.Engine {'short_code_1': 'h2x'}
h2x

If you want to re-appear this situation, write two script

  1. try import data using API
import requests
import json

def short(i):
    r = requests.post(
        "http://localhost:9119/api/shorten",
        headers={'Content-Type': 'Application/json'},
        data=json.dumps(
            {"long_url": "https://test.com/a/b/c/{i}".format(i=i)}
        )
    )

for i in range(10000):
    short(i)
  1. try to get next_short_code
#!/usr/bin/env python3
"""Script when you just want to run Pygmy API."""

from pygmy.core.initialize import initialize
initialize()

from pygmy.helpers.link_helper import next_short_code
import time
while True:
    print(next_short_code())

I'm writing this because that we use this and it cause the server near down(can't login using ssh). Is this related?

Improve "Already shortened URL" message

Shortening a pygy.co link is not allowed as that can result in a multi layer on shortening, which can be misused.
The message screen showing the error "URL is already a pygmy shortened link." also includes square brackets("[") which is a bug.

Example:

Screenshot 2019-06-16 at 10 26 28

python3 run.py: infinite loop

I setup from source, when running python3 run.py, infinite loop occurred, stop Terminal with output:

from django.core.wsgi import get_wsgi_application\nModuleNotFoundError: No module named 'django'\n[2022-07-17 18:32:48 +0700] [20252] [INFO] Worker exiting (pid: 20252)\n[2022-07-17 18:32:48 +0700] [20251] [INFO] Shutting down: Master\n[2022-07-17 18:32:48 +0700] [20251] [INFO] Reason: Worker failed to boot.\n')
Traceback (most recent call last):
File "run.py", line 46, in
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
ProcessLookupError: [Errno 3] No such process

Hope you could fix that.

Firefox & Chrome add ons

This is a feature request.

Any chance of creating a Firefox (for myself that would be best) or Chrome (I am sure other people use it) extension/add-on to use this?

Thank you.

Make website responsive and mobile friendly

Frankly, the website's 'Link Options' section looks horrible on mobile devices or resizing the browser. This should be fixed by using the CSS media rules and other responsive design practices. I do not have enough understanding of responsive design to fix it in minimum time.

Link options section

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.