Added click tracking

This commit is contained in:
xemeds
2020-08-22 15:36:09 +03:00
parent 1ef3c1b154
commit e65f43e401
9 changed files with 140 additions and 10 deletions
+38 -4
View File
@@ -5,7 +5,7 @@ from tiny0.config import WEBSITE_DOMAIN
from tiny0 import db from tiny0 import db
from tiny0.models import URL from tiny0.models import URL
# Validates a URL # Validates a url
def validate_URL(form, field): def validate_URL(form, field):
# Make sure the url is not too short or long # Make sure the url is not too short or long
if len(field.data) < 4 or len(field.data) > 2000: if len(field.data) < 4 or len(field.data) > 2000:
@@ -64,11 +64,45 @@ def validate_token(form, field):
# Raise a ValidationError # Raise a ValidationError
raise ValidationError("Token already exists") raise ValidationError("Token already exists")
# Validates a short url
def validate_short_URL(form, field):
# Make sure the short url is not too short or long
if len(field.data) < (len(WEBSITE_DOMAIN) + 7) or len(field.data) > (len(WEBSITE_DOMAIN) + 25):
return
# If the start of the short url is not valid
if (not(field.data.lower().startswith(WEBSITE_DOMAIN + "/"))
and not(field.data.lower().startswith("http://" + WEBSITE_DOMAIN + "/"))
and not(field.data.lower().startswith("https://" + WEBSITE_DOMAIN + "/"))):
# Raise a ValidationError
raise ValidationError("Invalid short URL")
# Get the token of the short url
if field.data.lower().startswith(WEBSITE_DOMAIN + "/"):
token = field.data[len(WEBSITE_DOMAIN) + 1:]
elif field.data.lower().startswith("http://" + WEBSITE_DOMAIN + "/"):
token = field.data[len(WEBSITE_DOMAIN) + 8:]
elif field.data.lower().startswith("https://" + WEBSITE_DOMAIN + "/"):
token = field.data[len(WEBSITE_DOMAIN) + 9:]
# If the token of the short url does not exist in the database
if not db.session.query(db.session.query(URL).filter_by(token=token).exists()).scalar():
# Raise a ValidationError
raise ValidationError("That short URL does not exists")
# After all the validation is done set the forms url value as the token
field.data = token
class URLForm(FlaskForm): class URLForm(FlaskForm):
url = StringField(validators=[DataRequired(), url = StringField(validators=[DataRequired(), Length(min=4, max=2000, message="Invalid URL length"), validate_URL])
Length(min=4, max=2000, message="Invalid URL length"),
validate_URL])
token = StringField(validators=[Optional(), Length(min=6, max=16, message="Invalid token length"), validate_token]) token = StringField(validators=[Optional(), Length(min=6, max=16, message="Invalid token length"), validate_token])
submit = SubmitField("Shorten") submit = SubmitField("Shorten")
class TrackerForm(FlaskForm):
url = StringField(validators=[DataRequired(), Length(min=len(WEBSITE_DOMAIN) + 7, max=len(WEBSITE_DOMAIN) + 25, message="Invalid short URL"), validate_short_URL])
submit = SubmitField("Track")
+2 -1
View File
@@ -4,6 +4,7 @@ class URL(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
token = db.Column(db.String(16), index=True, unique=True, nullable=False) token = db.Column(db.String(16), index=True, unique=True, nullable=False)
url = db.Column(db.String(2000), nullable=False) url = db.Column(db.String(2000), nullable=False)
clicks = db.Column(db.Integer, nullable=False, default=0)
def __repr__(self): def __repr__(self):
return f"'{self.id}' '{self.token}' '{self.url}'" return f"'{self.id}' '{self.token}' '{self.url} '{self.clicks}'"
+25 -2
View File
@@ -1,6 +1,6 @@
from flask import render_template, redirect, url_for from flask import render_template, redirect, url_for
from tiny0 import app, db from tiny0 import app, db
from tiny0.forms import URLForm from tiny0.forms import URLForm, TrackerForm
from tiny0.models import URL from tiny0.models import URL
from tiny0.token import gen_valid_token from tiny0.token import gen_valid_token
from tiny0.config import WEBSITE_DOMAIN from tiny0.config import WEBSITE_DOMAIN
@@ -35,7 +35,7 @@ def index():
# Return the url page with the shortened url # Return the url page with the shortened url
return render_template("url.html", url=WEBSITE_DOMAIN + "/" + token) return render_template("url.html", url=WEBSITE_DOMAIN + "/" + token)
# If the form was invalid or not submitted # Else if the form was invalid or not submitted
else: else:
# Return the index page with the form # Return the index page with the form
return render_template("index.html", form=form) return render_template("index.html", form=form)
@@ -53,9 +53,32 @@ def short_url(token):
# Else if the query response contained data # Else if the query response contained data
else: else:
# Addd one to the clicks of the shortened url
query.clicks += 1
db.session.commit()
# Redirect to the url of the token # Redirect to the url of the token
return redirect(query.url) return redirect(query.url)
# Click Tracker route
@app.route("/tracker", methods=['GET', 'POST'])
def tracker():
# Create a instance of the form
form = TrackerForm()
# If the form was valid
if form.validate_on_submit():
# Get the clicks of the given token
clicks = URL.query.filter_by(token=form.url.data).first().clicks
# Return the clicks page with the clicks of that token
return render_template("clicks.html", clicks=clicks)
# Else if the form was invalid or not submitted
else:
# Return the tracker page with the form
return render_template("tracker.html", form=form)
# Donate route # Donate route
@app.route("/donate") @app.route("/donate")
def donate(): def donate():
+37
View File
@@ -110,6 +110,43 @@ body {
margin-bottom: 2vh; margin-bottom: 2vh;
} }
.click-tracker {
text-align: center;
margin-top: 5vh;
}
.click-tracker a:link {
color: #bb86fc;
text-decoration: none;
font-size: 25px;
font-weight: bold;
}
.click-tracker a:visited {
color: #bb86fc;
}
.click-tracker a:hover {
color: #7b59a5;
}
.clicks-list {
text-align: center;
margin: 30vh 0 0 0;
}
.clicks-number {
color: #bb86fc;
font-size: 125px;
font-weight: bold;
}
.clicks-text {
color: #bb86fc;
font-size: 25px;
font-weight: bold;
}
.error-message { .error-message {
text-align: center; text-align: center;
color: #ffffff; color: #ffffff;
+11
View File
@@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block body %}
<ul class="clicks-list">
<li><p class="clicks-number">{{ clicks }}</p></li>
<li><p class="clicks-text">Click(s)</p></li>
</ul>
<div class="click-tracker">
<a href="{{ url_for('tracker') }}">Track Another</a>
</div>
{% endblock %}
+4 -1
View File
@@ -2,7 +2,7 @@
{% block body %} {% block body %}
<form method="POST" action="" class="url-form"> <form method="POST" action="" class="url-form">
{% if form.url.errors or form.token.errors %} {% if form.errors %}
<ul class="form-error-message"> <ul class="form-error-message">
{% for error in form.url.errors %} {% for error in form.url.errors %}
<li>{{ error }}</li> <li>{{ error }}</li>
@@ -24,4 +24,7 @@
<li>{{ form.submit(class="button") }}</li> <li>{{ form.submit(class="button") }}</li>
</ul> </ul>
</form> </form>
<div class="click-tracker">
<a href="{{ url_for('tracker') }}">Click Tracker</a>
</div>
{% endblock %} {% endblock %}
+2 -2
View File
@@ -14,7 +14,7 @@
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
{% if form %} {% if form %}
{% if not form.url.errors and not form.token.errors %} {% if not form.errors %}
<body onload="typeTitle()"> <body onload="typeTitle()">
<div class="headers"> <div class="headers">
<a class="title" id="title" href="{{ url_for('index') }}"></a> <a class="title" id="title" href="{{ url_for('index') }}"></a>
@@ -38,7 +38,7 @@
{% block body %}{% endblock %} {% block body %}{% endblock %}
{% if form %} {% if form %}
{% if not form.url.errors and not form.token.errors %} {% if not form.errors %}
<script> <script>
var i = 0; var i = 0;
var title_text = "tiny0"; var title_text = "tiny0";
+18
View File
@@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block body %}
<form method="POST" action="" class="url-form">
{% if form.errors %}
<ul class="form-error-message">
{% for error in form.url.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.hidden_tag() }}
<ul class='input-list'>
<li>{{ form.url(placeholder="Enter the short URL here", autofocus=true, class="url") }}</li>
<li>{{ form.submit(class="button") }}</li>
</ul>
</form>
{% endblock %}
+3
View File
@@ -7,4 +7,7 @@
<li><button onclick="copyURL()" class="button">Copy</button></li> <li><button onclick="copyURL()" class="button">Copy</button></li>
</ul> </ul>
</div> </div>
<div class="click-tracker">
<a href="{{ url_for('tracker') }}">Click Tracker</a>
</div>
{% endblock %} {% endblock %}