Add shortlinks
Officers can now create redirects from the root domain to other domains
This commit is contained in:
parent
ec95c168d9
commit
7e3d2191c6
7 changed files with 293 additions and 5 deletions
|
@ -3,9 +3,9 @@ import ulid
|
|||
import datetime
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from acmsite.models import User, Event
|
||||
from acmsite.models import Link, User, Event
|
||||
|
||||
from .forms import EventForm
|
||||
from .forms import EventForm, LinkForm
|
||||
from acmsite import db
|
||||
|
||||
|
||||
|
@ -76,7 +76,9 @@ def delete_event(id):
|
|||
@bp.route("/event/<string:id>", methods=["POST"])
|
||||
@login_required
|
||||
def update_create_event(id):
|
||||
|
||||
if not current_user.is_admin:
|
||||
flash("Unauthorized")
|
||||
return redirect(url_for("dashboard.home"))
|
||||
|
||||
name = request.form.get('name')
|
||||
description = request.form.get('description')
|
||||
|
@ -114,3 +116,57 @@ def update_create_event(id):
|
|||
|
||||
|
||||
return redirect(url_for("admin.events"))
|
||||
|
||||
@bp.route("/links")
|
||||
@login_required
|
||||
def links():
|
||||
if not current_user.is_admin:
|
||||
flash("Unauthorized")
|
||||
return redirect(url_for("dashboard.home"))
|
||||
|
||||
links = Link.query.all()
|
||||
form = LinkForm(request.form)
|
||||
|
||||
return render_template("admin/links.html", links=links, form=form)
|
||||
|
||||
@bp.route("/link/<string:id>")
|
||||
@login_required
|
||||
def link(id):
|
||||
if not current_user.is_admin:
|
||||
return {"status": "error", "message": "Unauthorized"}
|
||||
|
||||
link = Link.query.filter_by(id=id).first()
|
||||
|
||||
if link is None:
|
||||
return {"status": "error", "message": "Invalid ID"}
|
||||
|
||||
return link.create_json()
|
||||
|
||||
@bp.route("/link/<string:id>", methods=["POST"])
|
||||
@login_required
|
||||
def update_create_link(id):
|
||||
if not current_user.is_admin:
|
||||
flash("Unauthorized")
|
||||
return redirect(url_for("dashboard.home"))
|
||||
|
||||
slug = request.form.get('slug')
|
||||
destination = request.form.get('destination')
|
||||
|
||||
if id == '0':
|
||||
# new link
|
||||
l = Link(
|
||||
id=ulid.ulid(),
|
||||
slug=slug,
|
||||
destination=destination)
|
||||
db.session.add(l)
|
||||
db.session.commit()
|
||||
else:
|
||||
l = Link.query.filter_by(id=id).first()
|
||||
if l is None:
|
||||
flash("Invalid ID")
|
||||
return redirect(url_for("admin.links"))
|
||||
l.slug = slug
|
||||
l.destination = destination
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for("admin.links"))
|
||||
|
|
|
@ -10,3 +10,7 @@ class EventForm(FlaskForm):
|
|||
start_time = TimeField('Start Time')
|
||||
end_day = DateField('End Day', validators=[DataRequired()])
|
||||
end_time = TimeField('End Time')
|
||||
|
||||
class LinkForm(FlaskForm):
|
||||
slug = StringField("Slug", validators=[DataRequired()])
|
||||
destination = StringField("Destination", validators=[DataRequired()])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
from flask import Blueprint, render_template
|
||||
from acmsite.models import Event
|
||||
from flask import Blueprint, render_template, abort, redirect
|
||||
from acmsite.models import Event, Link
|
||||
|
||||
bp = Blueprint('main', __name__)
|
||||
|
||||
|
@ -16,3 +16,12 @@ def events():
|
|||
@bp.route("/join")
|
||||
def join():
|
||||
return render_template("join.html")
|
||||
|
||||
|
||||
@bp.route("/<string:slug>")
|
||||
def shortlink(slug):
|
||||
l = Link.query.filter_by(slug=slug).first()
|
||||
if l is None:
|
||||
abort(404)
|
||||
|
||||
return redirect(l.destination)
|
||||
|
|
|
@ -48,3 +48,16 @@ class Event(db.Model):
|
|||
"start_time": self.start_time.isoformat(),
|
||||
"end_time": self.end_time.isoformat(),
|
||||
}
|
||||
|
||||
class Link(db.Model):
|
||||
__tablename__ = "acm_links"
|
||||
id = Column(String, primary_key=True)
|
||||
slug = Column(String, nullable=False, unique=True)
|
||||
destination = Column(String, nullable=False)
|
||||
|
||||
def create_json(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"slug": self.slug,
|
||||
"destination": self.destination
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
{{ render_nav_item('admin.users', 'Member List')}}
|
||||
{{ render_nav_item('admin.events', 'Event List')}}
|
||||
{{ render_nav_item('admin.home', 'Bulk Mail Tool')}}
|
||||
{{ render_nav_item('admin.links', 'Shortlinks')}}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
{{ render_nav_item('dashboard.home', '<- Back To Site') }}
|
||||
|
|
171
acmsite/templates/admin/links.html
Normal file
171
acmsite/templates/admin/links.html
Normal file
|
@ -0,0 +1,171 @@
|
|||
{% extends 'admin/admin-layout.html' %}
|
||||
{% import 'bootstrap5/form.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>ACM Shortlinks</h1>
|
||||
<p>Use these to create redirects from the ACM site to other destinations. Make
|
||||
sure they don't conflict with existing routes -- avoid the following:</p>
|
||||
<ul>
|
||||
<li>/dashboard</li>
|
||||
<li>/admin</li>
|
||||
<li>/static</li>
|
||||
<li>/join</li>
|
||||
<li>/events</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slug</th>
|
||||
<th>Destination</th>
|
||||
<th><button type="button" class="btn btn-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#editModal"
|
||||
data-id="0">New</button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in links %}
|
||||
<tr>
|
||||
<td>{{ l.slug }}</td>
|
||||
<td>{{ l.destination }}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<a class="btn btn-primary dropdown-toggle"
|
||||
data-bs-toggle="dropdown" href="#"><span
|
||||
class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-item">
|
||||
<a href="#editModal" data-bs-toggle="modal"
|
||||
data-id="{{ l.id
|
||||
}}">Edit</a>
|
||||
</li>
|
||||
<li class="dropdown-item">
|
||||
<a href="#deleteModal" data-bs-toggle="modal"
|
||||
data-id="{{
|
||||
l.id}}">Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="modal" id="editModal" tabindex="-1" aria-labelledby="editModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="editModalLabel">Event</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="form" id="edit-form" action="/admin/events/0" role="form" method="post">
|
||||
<div class="modal-body">
|
||||
{{ form.csrf_token }}
|
||||
<div class="form-floating mb-3 required">
|
||||
{{ form.slug(class="form-control") }}
|
||||
{{ form.slug.label() }}
|
||||
</div>
|
||||
<div class="form-floating required">
|
||||
{{ form.destination(class="form-control") }}
|
||||
{{ form.destination.label() }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary" id="edit-save">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="deleteModal" tabindex="-1"
|
||||
aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="deleteModalLabel">Delete
|
||||
Event?</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="delete" data-bs-dismiss="modal" class=" btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.6.3.min.js') }}" charset="utf-8"></script>
|
||||
<script charset="utf-8">
|
||||
const deleteButton = document.getElementById("delete")
|
||||
const editButton = document.getElementById("edit-save")
|
||||
|
||||
deleteButton.addEventListener("click", (event) => {
|
||||
button = $(event.relatedTarget)
|
||||
id = deleteButton.dataset.id
|
||||
const deleteRequest = new Request(`/admin/link/${id}/delete`)
|
||||
|
||||
fetch(deleteRequest)
|
||||
.then(async (res) => {
|
||||
window.alert(await res.text())
|
||||
});
|
||||
});
|
||||
|
||||
$('#deleteModal').on('show.bs.modal', function(event) {
|
||||
var modal = $(this)
|
||||
var button = $(event.relatedTarget)
|
||||
var id = button.data("id")
|
||||
|
||||
// find delete button
|
||||
|
||||
delButton = document.getElementById("delete")
|
||||
delButton.dataset.id = id
|
||||
});
|
||||
|
||||
$('#editModal').on('show.bs.modal', function(event) {
|
||||
var modal = $(this)
|
||||
|
||||
// Zero all fields
|
||||
modal.find('#slug').val('')
|
||||
modal.find('#destination').val('')
|
||||
var button = $(event.relatedTarget)
|
||||
var slug,destination
|
||||
id = button.data('id')
|
||||
|
||||
saveButton = document.getElementById("edit-save")
|
||||
saveButton.dataset.id = id
|
||||
|
||||
editForm = document.getElementById("edit-form")
|
||||
editForm.action = "/admin/link/" + id
|
||||
|
||||
if (id) {
|
||||
$.get(`/admin/link/${id}`, (data) => {
|
||||
console.log(data)
|
||||
if (data.status == "error") {
|
||||
// This is a new event, do nothing!
|
||||
} else {
|
||||
slug = data.slug
|
||||
destination = data.destination
|
||||
}
|
||||
|
||||
modal.find('#slug').val(slug)
|
||||
modal.find('#destination').val(destination)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#deleteModal').on('hidden.bs.modal', function(event) {
|
||||
location.reload()
|
||||
});
|
||||
|
||||
$('#editModal').on('hidden.bs.modal', function(event) {
|
||||
location.reload()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
34
migrations/versions/69cccb10f676_add_shortlinks_models.py
Normal file
34
migrations/versions/69cccb10f676_add_shortlinks_models.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
"""add shortlinks models
|
||||
|
||||
Revision ID: 69cccb10f676
|
||||
Revises: 6d239e987242
|
||||
Create Date: 2024-03-21 10:23:18.010881
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '69cccb10f676'
|
||||
down_revision = '6d239e987242'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('acm_links',
|
||||
sa.Column('id', sa.String(), nullable=False),
|
||||
sa.Column('slug', sa.String(), nullable=False),
|
||||
sa.Column('destination', sa.String(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('slug')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('acm_links')
|
||||
# ### end Alembic commands ###
|
Loading…
Add table
Reference in a new issue