~jshsj/python_link_shortener

f467640a576f01d14ee2706e27ef6a9d95492c76 — JSH 1 year, 11 months ago master
initial commit
7 files changed, 278 insertions(+), 0 deletions(-)

A .gitignore
A Pipfile
A server.py
A templates/error.html
A templates/home.html
A urls.db
A vars.txt
A  => .gitignore +2 -0
@@ 1,2 @@
.vscode
Pipfile.lock

A  => Pipfile +14 -0
@@ 1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "*"
flask-wtf = "*"
flask-limiter = "*"

[dev-packages]

[requires]
python_version = "3.7"

A  => server.py +97 -0
@@ 1,97 @@
from flask import Flask, render_template, request, redirect, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import exc
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
from wtforms import StringField, BooleanField
from wtforms.validators import DataRequired, Length
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address


import base64, sys, os

app = Flask(__name__)
host = 'http://localhost:5000'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///urls.db'
app.secret_key = "sadasdas"

with open('vars.txt', 'r') as infile:
    content = infile.read()
    content = content.split('::')
    host = content[0]
    app.secret_key = content[1]

csrf = CSRFProtect(app)
db = SQLAlchemy(app)
limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits = ["60 per minute", "1 per second"]
    )





class ShortUrl(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    original_url = db.Column(db.String(1000), unique=False, nullable=False)

    def __repr__(self):
        return f'Url: {self.original_url}'

class CustomUrl(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    original_url = db.Column(db.String(1000), unique=False, nullable=False)
    custom_url = db.Column(db.String(1000), unique=True, nullable=False)

class UrlInputForm(FlaskForm):
    url = StringField('URL', validators=[DataRequired(), Length(max=1000)])
    use_custom = BooleanField('Eigene URL benutzen?')
    custom_url = StringField('Eigene URL', validators=[Length(max=900)])


@app.route('/', methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
        form = UrlInputForm()
        if not form.validate_on_submit():
            return render_template('home.html', form=form)
        url = ''
        original_url = form.url.data
        if (form.use_custom.data == True):
            try:
                custom_url = form.custom_url.data
                if custom_url == "":
                    return render_template('home.html', form=form)
                url = CustomUrl(original_url=original_url, custom_url=custom_url)        
                db.session.add(url)
                db.session.commit()
                return render_template('home.html', custom_url=host + '/c/' + custom_url)
            except exc.IntegrityError as e:
                print(e)
                db.session.rollback()
                return render_template('home.html', error="Deine URL ist leider schon benutzt. Probier eine andere.")

        url = ShortUrl(original_url = original_url)
        db.session.add(url)
        db.session.commit()
        short_url = base64.urlsafe_b64encode(bytes([url.id])).decode('utf-8')
        return render_template('home.html', custom_url=host + "/s/" + short_url)

    form = UrlInputForm()
    return render_template('home.html', form=form)

@app.route('/c/<custom_url>')
def redirect_custom_url(custom_url):
    original_url = CustomUrl.query.filter_by(custom_url=custom_url).first().original_url
    return redirect(original_url)

@app.route('/s/<string:short_url>')
def redirect_short_url(short_url):
    decoded_bytes = base64.urlsafe_b64decode(short_url)
    decoded_id = int.from_bytes(decoded_bytes, byteorder=sys.byteorder)
    original_url = ShortUrl.query.get(decoded_id).original_url
    return redirect(original_url)


A  => templates/error.html +19 -0
@@ 1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Url Shortener</title>
</head>
<body>
    <section class="container">
        {% if error %}
            <p>{{ error }}</p>
        {% else %}
            <p>There was an error. Please try again later</p>
        {% endif %}
    </section>
    
</body>
<
\ No newline at end of file

A  => templates/home.html +145 -0
@@ 1,145 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Url Shortener</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Fira+Sans:300,400">
    <style>
        body {
            font-family: "Fira Sans", sans-serif;
            font-size: 16px;
        }

        * {
            box-sizing: border-box;
        }
        .container {
            width: 80%;
            max-width: 1024px;
            margin: 0 auto;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }

        .shortener-wrap {
            width: 100%;
            max-width: 640px;
        }
        .form-input {
            display: flex;
            height: 60px;
            align-items: center;
            margin: 10px 0;
            position: relative;
        }

        label {
            width: 20%;
            min-width: 80px;
            font-weight: 300;
        }

        input {
            width: 80%;
            min-width: 250px;
            border: none;
            font-size: 16px;
            background: rgba(255, 175, 56, 0.8);
            padding: 8px 5px;
            display: block;
            position: relative;
            height: 40px;
        }

        input[type='submit'] {
            width: 150px;
        }

        input[type='checkbox'] {
            display: none;
        }

        #checkbox-field {
                display: block;
                height: 40px;
                width: 40px;
                background: rgba(255,175,56,0.8);
            }

        #checkbox-field.active:after {
            content: '\2714';
            position: relative;
            font-size: 40px;
            left: 5px;

            line-height: 40px;
        }

        shortened-wrap {
            width: 100%;
            margin-top: 50px;
            font-size: 18px;
        }

        a {
            text-decoration: none;
            color: rgba(255,175,56);
        }
        
        

        
    </style>
</head>
<body>
    <section class="container">
        {% if not custom_url %}
        <div class="shortener-wrap">
            <h1>URL Shortener</h1>
            <form method="post" action="">
                {{ form.csrf_token }}
                <div class="form-input">{{ form.url.label }} {{ form.url}}</div>
                <div class="form-input">{{ form.use_custom.label }} <span id="checkbox-field"></span>{{ form.use_custom(checked=False)}}</div>
                <div class="form-input" id="custom_url_field">{{ form.custom_url.label }} {{ form.custom_url}}</div>
                <input type="submit" value="URL generieren"/>
            </form>
        </div>
        {% else %}
        <div class="shortened-wrap">
            <p>Und hier ist deine URL: <a href="{{ custom_url }}">{{ custom_url }}</a></p>
            <a href="/">Noch eine!</a>
        </div>
        {% endif %}
        {% if error %}
        <p class="error">{{ error }}</p>
        {% endif %}
    </section>

    <script>
        let checkbox = document.getElementById('checkbox-field');
        let use_custom = document.getElementById('use_custom');
        let custom_url_field = document.getElementById('custom_url_field')
        let is_checked = use_custom.checked
        is_checked ? custom_url_field.style.display = 'flex' : custom_url_field.style.display = 'none'
        checkbox.addEventListener('click', function(e) {
            if (is_checked) {
                checkbox.classList.remove('active');
                use_custom.checked = false
                custom_url_field.style.display = 'none'
            } else {
                checkbox.classList.add('active')
                use_custom.checked = true
                custom_url_field.style.display = 'flex'
            }
            is_checked = !is_checked
        })

    </script>
    
</body>
</html>
\ No newline at end of file

A  => urls.db +0 -0

A  => vars.txt +1 -0
@@ 1,1 @@
https://test.test::324124asfsgdsgs
\ No newline at end of file