diff --git a/app/actions.py b/app/actions.py index c760356..2a40652 100644 --- a/app/actions.py +++ b/app/actions.py @@ -275,4 +275,43 @@ def service_delete(): flasher(f"Service {svc['display_name']} deleted successfully.", color='success') return redirect(url_for('dashboard.list_services')) + + +@bp.route('/invites/new', methods=['POST']) +@admin_login_required +def invite_create(): + link = request.form['link'] if 'link' in request.form else None + code = request.form['code'] if 'code' in request.form else None + + c = get_db().cursor() + c.execute(''' + INSERT INTO Invites (app_id, service, link, code) + VALUES (?,?,?,?) + ''', [ + request.form['app_id'], + request.form['service'], + link, + code + ]) + + c.connection.commit() + + flasher('Created invite successfully!', color='success') + + return redirect(url_for('dashboard.list_invites')) + + +@bp.route('/invites/delete/') +@admin_login_required +def invite_delete(inv_id): + c = get_db().cursor() + c.execute(''' + DELETE FROM Invites WHERE id=? + ''', [inv_id]) + + c.connection.commit() + + flasher('Deleted invite successfully!', color='success') + + return redirect(url_for('dashboard.list_invites')) diff --git a/app/dashboard.py b/app/dashboard.py index 921df2e..502601a 100644 --- a/app/dashboard.py +++ b/app/dashboard.py @@ -389,6 +389,28 @@ def edit_config(): strtimes=strtimes ) +@bp.route('/invites') +@admin_login_required +def list_invites(): + c = get_db().cursor() + + c.execute(''' + SELECT i.id, i.app_id, i.service, i.code, i.link, a.email + FROM Invites i + LEFT JOIN Applicants a ON a.user_id=i.app_id + ''') + + rows = c.fetchall() + + if rows is None: + rows = [] + + return render_template( + 'dashboard/invites.html', + session=session, + invites=rows + ) + @bp.after_request def no_cache(response): response.headers['Cache-Control'] = 'no-store' diff --git a/app/hacker.py b/app/hacker.py index 60a3686..8fb56e0 100644 --- a/app/hacker.py +++ b/app/hacker.py @@ -309,14 +309,18 @@ def status(): def invites(): appl, appl_text = resolve_application(session['email']) - if appl['completed']: - completed_time = arrow.get(appl['completed_time']).format('MMM D, YYYY hh:mm a') - else: - completed_time = '' + c = get_db().cursor() + c.execute(''' + SELECT * FROM Invites WHERE app_id=? + ''', [appl['user_id']]) + + rows = c.fetchall() + if rows is None: + rows = [] return render_template( 'hacker/invites.html', appl=appl_text, - completed_time=completed_time, + invites=rows, login_type=capitalize_login_provider() ) diff --git a/app/schema.sql b/app/schema.sql index d00cdf7..f1c9204 100644 --- a/app/schema.sql +++ b/app/schema.sql @@ -68,7 +68,11 @@ CREATE TABLE Votes ( ); CREATE TABLE Invites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, app_id TEXT NOT NULL, + service TEXT NOT NULL, + code TEXT, + link TEXT, FOREIGN KEY (app_id) REFERENCES Applicants (user_id) ON DELETE CASCADE ON UPDATE NO ACTION diff --git a/app/services.py b/app/services.py index fbaacd5..b34f15c 100644 --- a/app/services.py +++ b/app/services.py @@ -231,11 +231,76 @@ def api_get_all_applicants_csv(): mimetype='text/csv' ) +@bp.route('/invites/create', methods=['POST']) +@service_auth_required +def api_invite_create(): + ''' + Creates an invite from POSTed JSON. + + JSON body: +
+    {
+        "app_id": "applicant_id",
+        "service": "Service Name",
+        "code": "(optional) code",
+        "link": "(optional) link",
+    }
+ ''' + + link = request.json['link'] if 'link' in request.json else None + code = request.json['code'] if 'code' in request.json else None + + if link is None and code is None: + return create_response({}, ok=False, message='Must have either link or code!') + + c = get_db().cursor() + c.execute(''' + INSERT INTO Invites (app_id, service, link, code) + VALUES (?,?,?,?) + ''', [ + request.json['app_id'], + request.json['service'], + link, + code + ]) + + c.connection.commit() + + return create_response({}) + +@bp.route('/invites/list') +@service_auth_required +def api_invite_list(): + ''' + List all the invites in the database. + + With an optional parameter `id`, this will return only the invites for the specified `user_id`. + ''' + + c = get_db().cursor() + + if request.args.get('id') is not None: + c.execute(''' + SELECT * FROM Invites WHERE app_id=? + ''', [request.args.get('id')]) + else: + c.execute(''' + SELECT * FROM Invites + ''') + + rows = c.fetchall() + if rows is None: + rows = [] + + return create_response(rows_to_objs(rows)) + api_routes = [ api_config, api_download_backup, api_get_by_id, api_get_by_email, api_get_all_applicants, - api_get_all_applicants_csv + api_get_all_applicants_csv, + api_invite_create, + api_invite_list ] diff --git a/app/templates/dashboard/base.html b/app/templates/dashboard/base.html index c8f749d..ab52210 100644 --- a/app/templates/dashboard/base.html +++ b/app/templates/dashboard/base.html @@ -36,19 +36,22 @@ diff --git a/app/templates/dashboard/invites.html b/app/templates/dashboard/invites.html new file mode 100644 index 0000000..09e48ed --- /dev/null +++ b/app/templates/dashboard/invites.html @@ -0,0 +1,90 @@ +{% extends "dashboard/base.html" %} + +{% block dashboard_content %} + +
+
+ + + + + + + + + + + + + {% for invite in invites %} + + + + + + + + + + {% else %} + + + + {% endfor %} + +
Applicant IDEmailServiceCodeLinkDelete
{{invite.app_id}}{{invite.email}}{{invite.service }}{{invite.code}}{{ invite.link }} + × +
+ No invites in DB +
+
+ +
+

Create new invite

+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+{% endblock %} diff --git a/app/templates/dashboard/table.html b/app/templates/dashboard/table.html index 4214ef4..cb95cbd 100644 --- a/app/templates/dashboard/table.html +++ b/app/templates/dashboard/table.html @@ -58,7 +58,7 @@ {% for row in rows %} - {{ row['id'] }} + {{ row['user_id'] }} {{ boolean_val(row['verified']) }} {{ boolean_val(row['completed']) }} {{ row['first_name'] }} {{ row['last_name'] }} @@ -75,7 +75,7 @@ {% else %} - No applicants in DB, upload some from the admin interface. + No applicants in DB. {% endfor %} diff --git a/app/templates/hacker/invites.html b/app/templates/hacker/invites.html index dcc324a..e93144d 100644 --- a/app/templates/hacker/invites.html +++ b/app/templates/hacker/invites.html @@ -1,4 +1,29 @@ {% extends "hacker/base.html" %} {% block hacker_content %} + +

Application

+ +{% if invites | length == 0 %} +

No invites at this time. Check back later!

+{% else %} +

+ {{ invites|length }} invite{% if invites | length > 1 %}s{%endif%} +

+ +{% for invite in invites %} +
+

{{ invite['service'] }}

+

+ {% if invite['code'] %} + Invite code: {{ invite['code'] }}
+ {% endif %} + {% if invite['link'] %} + Link: {{ invite['link'] }} + {% endif %} +

+
+{% endfor %} + +{% endif %} {% endblock %}