Skip to content

Commit

Permalink
Merge pull request #376 from Fer-Bar/urlshortener
Browse files Browse the repository at this point in the history
Added a url shortener app for everyone can use it.
  • Loading branch information
gantavyamalviya authored Oct 11, 2022
2 parents 7779d9b + 5abfdaa commit ab26c88
Show file tree
Hide file tree
Showing 19 changed files with 510 additions and 0 deletions.
1 change: 1 addition & 0 deletions Url Shortener/.flaskenv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FLASK_APP=main.py
80 changes: 80 additions & 0 deletions Url Shortener/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 📎UrlShortener
> This is an UrlShortener built with Flask and Postgresql.
>
>

# 👨‍💻Installation
## 📄Pre-Requirements
- Python Installed (Recommended version 3.8 or above)
- Pip Package Manager (pip)
## ⚙️How to use it?
1. Download this repository with git clone or by clicking the download as archive on this page

```
git clone https://github.com/Fer-Bar/UrlShortener.git
```
Go to the project directory.
```
cd url_shortener
```
2. Create a virtual environment:
### 🪟Windows:
```
py -m venv venv
```
Once created you can activate it.
```
venv\Scripts\activate.bat
```
### 🐧Unix or MacOS:
```
pip install virtualenv
virtualenv venv
```
Once created you can activate it.
```
source venv/bin/activate
```
3. Install dependencies with `pip install -r requirements.txt`. Make sure everything is installed properly with `pip freeze`.
4. The last step is run the [main.py](main.py) file, if you want you can change the host and the port. For example:
```
app.run(host='localhost', port=9000, debug=True)
```
## 🧪 Tests
- To run tests, run the following command:
```
python -m pytest -v
```
## 🔃Migrations
- To run the migrations, just run the following commands:
This command adds a `migrations` directory in the root of your project. This is a directory where all the migration scripts are going to be stored.
```
flask db init
```
The next step in the process is to create an initial migration, using the `migrate` command:
```
flask db migrate -m "Initial migration."
```
Then you can apply the migration to the database:
```
flask db migrate -m "Initial migration."
```
## 😎 Author
👤 **Fernando Barrientos**
<!---* Website: xadec
-->
* Website: [fer-bar.github.io](https://fer-bar.github.io/Portfolio/)
* Github: [Fer-Bar](https://github.com/Fer-Bar)
33 changes: 33 additions & 0 deletions Url Shortener/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from decouple import config


DATABASE_URI = config("DATABASE_URL")
if DATABASE_URI.startswith("postgres://"):
DATABASE_URI = DATABASE_URI.replace("postgres://", "postgresql://", 1)


class Config(object):
DEBUG = False
TESTING = False
CSRF_ENABLED = True
SECRET_KEY = config('SECRET_KEY', default='guess-me')
SQLALCHEMY_DATABASE_URI = DATABASE_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False


class ProductionConfig(Config):
DEBUG = False


class StagingConfig(Config):
DEVELOPMENT = True
DEBUG = True


class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True


class TestingConfig(Config):
TESTING = True
4 changes: 4 additions & 0 deletions Url Shortener/core/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SECRET_KEY=b48d8c4c124c654036d0250be7ff9dbb
DATABASE_URL=sqlite:///shorty.db
APP_SETTINGS=config.DevelopmentConfig
FLASK_APP=core
13 changes: 13 additions & 0 deletions Url Shortener/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from decouple import config


app = Flask(__name__)
app.config.from_object(config("APP_SETTINGS"))

db = SQLAlchemy(app)
migrate = Migrate(app, db)

from core import routes
19 changes: 19 additions & 0 deletions Url Shortener/core/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from core import db
from datetime import datetime


class ShortUrl(db.Model):
"""
Class that represents a shorturl of our application
The following attributes of a shorturl are stored in this table:
* original_url - url who user give us
* short_id - short_id that serves to redirect to the original_url
* created_at - creation date
"""
id = db.Column(db.Integer, primary_key=True)
original_url = db.Column(db.String(500), nullable=False)
short_id = db.Column(db.String(20), nullable=False, unique=True)
created_at = db.Column(db.DateTime(), default=datetime.now(), nullable=False)

def __repr__(self):
return f'ShortUrl: {self.short_id}'
62 changes: 62 additions & 0 deletions Url Shortener/core/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import string
import validators

from datetime import datetime
from core.models import ShortUrl
from core import app, db
from random import choice
from flask import render_template, request, flash, redirect, url_for


def generate_short_id(num_of_chars: int) -> str:
"""Function to generate short_id of specified number of characters"""
return ''.join(choice(string.ascii_letters+string.digits) for _ in range(num_of_chars))


@app.route('/', methods=['GET', 'POST'])
def index():
"""
The main view receives POST and GET requests
"""
if request.method == 'POST':
url = request.form['url']
short_id = request.form['custom_id']

if short_id and ShortUrl.query.filter_by(short_id=short_id).first() is not None:
flash('Please enter different custom id!')
return redirect(url_for('index'))

if not validators.url(url):
flash('Enter a valid url.')
return redirect(url_for('index'))

if not url:
flash('The URL is required!')
return redirect(url_for('index'))

if not short_id:
short_id = generate_short_id(8)

new_link = ShortUrl(original_url=url,
short_id=short_id,
created_at=datetime.now())
db.session.add(new_link)
db.session.commit()
short_url = request.host_url + short_id

return render_template('index.html', short_url=short_url)

return render_template('index.html')

@app.route('/<short_id>')
def redirect_url(short_id: str):
"""
This view redirects to the original_url address \n
Only receives GET requests
"""
link = ShortUrl.query.filter_by(short_id=short_id).first()
if link:
return redirect(link.original_url)
else:
flash('Invalid URL')
return redirect(url_for('index'))
Binary file added Url Shortener/core/shorty.db
Binary file not shown.
27 changes: 27 additions & 0 deletions Url Shortener/core/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">

<title>{% block title %} {% endblock %}</title>
</head>
<body style="background-color: #112A6c">
<div style="font-family: monospace, sans-serif;" class="container mt-3">
{% for message in get_flashed_messages() %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-eMNCOe7tC1doHpGoWe/6oMVemdAVTMs2xqW4mwXrXsW0L84Iytr2wi5v2QjrP/xp" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-cn7l7gDp0eyniUwwAZgrzD06kc/tftFf19TOAs2zVinnD/C7E91j9yyk5//jjpt/" crossorigin="anonymous"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions Url Shortener/core/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends 'base.html' %}

{% block content %}
<h1 style="color: #ffffff"class="text-center mb-3">{% block title %} Welcome to Linkcito {% endblock %}</h1>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<form method="post" action="{{url_for('index')}}">
<div class="form-floating mb-3">
<input type="text" name="url" id="url"
placeholder="Enter looooooooooooong URL" class="form-control"
value="{{ request.form['url'] }}" autofocus></input>
<label style="color: #242424" for="url">URL</label>
</div>
<div class="form-floating mb-3">
<input type="text" name="custom_id" id="custom_id"
placeholder="Want to customise? (optional)" class="form-control"
value="{{ request.form['custom_id'] }}"></input>
<label style="color: #242424" for="custom_id">Custom Short ID</label>
</div>

<div class="form-group text-center">
<button style="background-color: #F95B5C !important; border-color: #F95B5C !important"
type="submit" class="btn btn-lg btn-primary">Shorten</button>
</div>
</form>

{% if short_url %}
<hr>
<h5 class="text-center" style="color: #ffffff">This is your short URL:</h5 style="color:">
<div class="text-center"><a style="color:#f8f9fa"href="{{ short_url }}" target="_blank">{{ short_url }}</a></div>
{% endif %}
</div>
<div class="col-md-2"></div>
</div>
{% endblock %}
4 changes: 4 additions & 0 deletions Url Shortener/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from core import app

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
1 change: 1 addition & 0 deletions Url Shortener/migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
50 changes: 50 additions & 0 deletions Url Shortener/migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Loading

0 comments on commit ab26c88

Please sign in to comment.