Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Pylint

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
pip install -r requirements.txt
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
2 changes: 2 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[MASTER]
ignore-patterns=migrations/*.py
8 changes: 5 additions & 3 deletions acm_url/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os


class Config(object):
SECRET_KEY=os.environ.get('SECRET_KEY') or 'dev'
SQLALCHEMY_DATABASE_URI=os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'acm_url.sqlite')
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'acm_url.sqlite')
SQLALCHEMY_TRACK_MODIFICATIONS = False
POSTS_PER_PAGE=10
POSTS_PER_PAGE = 10
20 changes: 15 additions & 5 deletions acm_url/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Regexp, Optional, InputRequired


class CreateForm(FlaskForm):
vanity = StringField('Custom vanity (optional)', validators=[Optional(), Regexp('^[\w-]+$', message="Short name must only containt letters, digits, and dashes.")])
url = StringField('Enter long URL to make short', validators=[DataRequired(message="A URL was not entered.")])
submit = SubmitField('Make URL')
vanity = StringField(
'Custom vanity (optional)',
validators=[
Optional(),
Regexp(
'^[\\w-]+$',
message="Short name must only containt letters, digits, and dashes.")])
url = StringField('Enter long URL to make short', validators=[
DataRequired(message="A URL was not entered.")])
submit = SubmitField('Make URL')


class PasswordForm(FlaskForm):
password = PasswordField('Password', validators=[InputRequired(message="You must submit the password to create an URL.")])
submit = SubmitField('Submit')
password = PasswordField('Password', validators=[InputRequired(
message="You must submit the password to create an URL.")])
submit = SubmitField('Submit')
84 changes: 61 additions & 23 deletions acm_url/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,44 @@
from acm_url.schema import URL
from sqlalchemy import func

# Default endpoint. If logged in, redirect to create. Otherwise, prompt them for password.
# Default endpoint. If logged in, redirect to create. Otherwise, prompt them for password.
# If correct, redirect to create, otherwise, stay here.


@app.route('/', methods=('GET', 'POST'))
def index():
user_id = session.get('user_id')

if user_id is None:
password_form = PasswordForm()
password_form = PasswordForm()
if password_form.validate_on_submit():
pwd = password_form.password.data or ""
if check_password_hash(os.environ.get('OFFICER_PWD'), pwd):
session.clear()
session.permanent = True
session['user_id'] = ''.join(secrets.choice(string.digits) for i in range(5))
session['user_id'] = ''.join(
secrets.choice(string.digits) for i in range(5))
return redirect(url_for('create'))
else:
session.clear()
return render_template('password.html', form=password_form, error="Incorrect")
return render_template(
'password.html',
form=password_form,
error="Incorrect")
else:
return render_template('password.html', form=password_form)

return redirect(url_for('create'))

# Endpoint for creating a new vanity url. GET request returns the form.
# POST request validates and creates the new url in the DB.
# POST request validates and creates the new url in the DB.


@app.route('/create', methods=('GET', 'POST'))
def create():
if session.get('user_id') is None:
return redirect(url_for('index'))

create_form = CreateForm()

if create_form.validate_on_submit():
Expand All @@ -49,21 +57,36 @@ def create():
url = 'https://' + url

if not vanity:
vanity = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for i in range(10))
old_entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()
vanity = ''.join(
secrets.choice(
string.ascii_lowercase +
string.digits) for i in range(10))
old_entry = URL.query.filter(func.lower(
URL.vanity) == func.lower(vanity)).first()

while old_entry is not None:
vanity = ''.join(secrets.choice(string.ascii_lowercase + string.digits) for i in range(12))
old_entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()
vanity = ''.join(
secrets.choice(
string.ascii_lowercase +
string.digits) for i in range(12))
old_entry = URL.query.filter(func.lower(
URL.vanity) == func.lower(vanity)).first()
else:
if vanity.lower() == 'create':
return render_template('url.html', form=create_form, error="You cannot use this short name. Please try again.")

old_entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()

return render_template(
'url.html',
form=create_form,
error="You cannot use this short name. Please try again.")

old_entry = URL.query.filter(func.lower(
URL.vanity) == func.lower(vanity)).first()

if old_entry is not None:
return render_template('url.html', form=create_form, error="Short name already taken! Please try again.")

return render_template(
'url.html',
form=create_form,
error="Short name already taken! Please try again.")

new_url = URL(vanity=vanity, url=url)
db.session.add(new_url)
db.session.commit()
Expand All @@ -72,31 +95,44 @@ def create():
return render_template('url.html', form=create_form)

# Endpoint for accessing a vanity url. A simple redirect to the URL on file.


@app.route('/<vanity>')
def vanity(vanity):
entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()
entry = URL.query.filter(func.lower(URL.vanity) ==
func.lower(vanity)).first()

if entry is None:
return render_template('404.html')

entry.visit_count = entry.visit_count + 1
db.session.commit()

# return redirect
return redirect(entry.url, code=302)


@app.route('/all', methods=('GET', 'POST'))
def all():
page = request.args.get('page', 1, type=int)
links = URL.query.order_by(URL.visit_count.desc()).paginate(page, app.config['POSTS_PER_PAGE'], False)
links = URL.query.order_by(
URL.visit_count.desc()).paginate(
page, app.config['POSTS_PER_PAGE'], False)
next_url = url_for('all', page=links.next_num) if links.has_next else None
prev_url = url_for('all', page=links.prev_num) if links.has_prev else None
return render_template('links.html', links=links.items,next_url=next_url, prev_url=prev_url)
return render_template(
'links.html',
links=links.items,
next_url=next_url,
prev_url=prev_url)

# Endpoint for deleting a vanity url.


@app.route('/delete/<vanity>', methods=['POST'])
def delete(vanity):
entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()
entry = URL.query.filter(func.lower(URL.vanity) ==
func.lower(vanity)).first()

if entry is None:
return render_template('404.html')
Expand All @@ -105,14 +141,16 @@ def delete(vanity):
db.session.commit()
return redirect(url_for('all'))


@app.route('/edit', methods=['POST'])
def edit():
# Grab request data
vanity = request.form['vanity']
url = request.form['url']

# Check if vanity exists
entry = URL.query.filter(func.lower(URL.vanity) == func.lower(vanity)).first()
entry = URL.query.filter(func.lower(URL.vanity) ==
func.lower(vanity)).first()
if entry is None:
return f"No vanity found with name {vanity}", 404

Expand Down
18 changes: 10 additions & 8 deletions acm_url/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

# url table stores vanity to url. Also tracks when it was created and how many
# visits the vanity url has been used.


class URL(db.Model):
id = db.Column(db.Integer, primary_key=True)
vanity = db.Column(db.String(120), index=True, unique=True, nullable=False)
url = db.Column(db.Text, nullable=False)
created = db.Column(db.DateTime, server_default=func.now(), nullable=False)
visit_count = db.Column(db.Integer, default=0, nullable=False)

def __repr__(self):
return '<URL {}>'.format(self.vanity)
id = db.Column(db.Integer, primary_key=True)
vanity = db.Column(db.String(120), index=True, unique=True, nullable=False)
url = db.Column(db.Text, nullable=False)
created = db.Column(db.DateTime, server_default=func.now(), nullable=False)
visit_count = db.Column(db.Integer, default=0, nullable=False)

def __repr__(self):
return '<URL {}>'.format(self.vanity)