Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
__pychache__
|
||||||
|
config.toml
|
||||||
|
database.db
|
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# PyPolls
|
||||||
|
|
||||||
|
## API endpoints
|
||||||
|
Description | Method | Endpoint
|
||||||
|
----------- | :-------: | ---------------------------------------
|
||||||
|
Create new poll | POST | /api/v1/polls/create/<choice/checkboxes>
|
||||||
|
Vote | POST, PUT | /api/v1/polls/<uuid:poll_id>?id=...<br />/api/v1/polls/<uuid:poll_id>?0=true&1=false&2=true
|
||||||
|
Get results and variants | GET | /api/v1/polls/<uuid:poll_id>
|
||||||
|
Stop poll | POST, PUT | /api/v1/stop/<uuid:poll_id>[?token=...] [Authorization: Bearer ...]
|
||||||
|
Delete poll | DELETE | /api/v1/polls/<uuid:poll_id>[?token=...] [Authorization: Bearer ...]
|
2
config.example.toml
Executable file
2
config.example.toml
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
[database]
|
||||||
|
file = "/path/to/database.db"
|
59
flake.lock
generated
Normal file
59
flake.lock
generated
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1685518550,
|
||||||
|
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1685399834,
|
||||||
|
"narHash": "sha256-Lt7//5snriXSdJo5hlVcDkpERL1piiih0UXIz1RUcC4=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "58c85835512b0db938600b6fe13cc3e3dc4b364e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
2
requirements.txt
Executable file
2
requirements.txt
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
Flask==2.0.2
|
||||||
|
tomli==2.0.1
|
5
shell.nix
Executable file
5
shell.nix
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
# nativeBuildInputs is usually what you want -- tools you need to run
|
||||||
|
nativeBuildInputs = [ pkgs.buildPackages.python311 pkgs.buildPackages.python311Packages.flask pkgs.buildPackages.python311Packages.tomli pkgs.sqlite ];
|
||||||
|
}
|
4
src/.gitignore
vendored
Normal file
4
src/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
config.toml
|
||||||
|
database.db
|
||||||
|
tr(uuid.uuid4())
|
||||||
|
__pycache__
|
265
src/app.py
Executable file
265
src/app.py
Executable file
@@ -0,0 +1,265 @@
|
|||||||
|
from re import A
|
||||||
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
abort,
|
||||||
|
request,
|
||||||
|
jsonify,
|
||||||
|
render_template,
|
||||||
|
make_response,
|
||||||
|
Response,
|
||||||
|
)
|
||||||
|
import tomli
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from db import *
|
||||||
|
|
||||||
|
protocol = "http"
|
||||||
|
domain = "localhost:5000"
|
||||||
|
|
||||||
|
with open("config.toml", mode="rb") as fp:
|
||||||
|
cfg = tomli.load(fp)
|
||||||
|
|
||||||
|
initdb(db())
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# APIs
|
||||||
|
def get_poll_type(n: int):
|
||||||
|
if n == 1 or n == 2:
|
||||||
|
return("choice")
|
||||||
|
elif n == 3 or n == 9:
|
||||||
|
return("checkboxes")
|
||||||
|
else:
|
||||||
|
return("unknown")
|
||||||
|
|
||||||
|
def create(title, creator, description, entrs, polltype, alias = ""):
|
||||||
|
poll_id = str(uuid.uuid4())
|
||||||
|
admin_token = ''.join(random.choices(string.ascii_letters + string.digits, k=24))
|
||||||
|
if creator == None:
|
||||||
|
creator = ""
|
||||||
|
if description == None:
|
||||||
|
description = ""
|
||||||
|
conn = db()
|
||||||
|
if polltype == "checkboxes":
|
||||||
|
polltype = "3"
|
||||||
|
elif polltype == "choice":
|
||||||
|
polltype = "1"
|
||||||
|
for i in entrs:
|
||||||
|
conn.execute(f"INSERT INTO variants VALUES ('{poll_id}', '{i}', 0)")
|
||||||
|
conn.execute(
|
||||||
|
f"INSERT INTO polls VALUES ('{poll_id}', '{admin_token}', '{title}', '{creator}', '{description}', {polltype}, '{alias}', 0)"
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return (poll_id, admin_token)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/v1/polls/create/<string:poll_type>", methods=["POST"])
|
||||||
|
def api_create(poll_type):
|
||||||
|
if poll_type != "checkboxes" and poll_type != "choice":
|
||||||
|
return '{"error":"Uknown poll type"}', 400
|
||||||
|
|
||||||
|
if request.is_json == False:
|
||||||
|
req = request.form.to_dict()
|
||||||
|
else:
|
||||||
|
req = request.get_json()
|
||||||
|
if req.get("title") and req.get("values"):
|
||||||
|
ret = create(req["title"], req.get("author"), req.get("description"), req["values"], poll_type)
|
||||||
|
return '{"id":"%s","token":"%s"}' % (ret[0], ret[1]), 200
|
||||||
|
else:
|
||||||
|
return '{"error":"Title and values are required"}', 400
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/v1/polls/<uuid:poll_id>", methods=["DELETE"])
|
||||||
|
def deletepoll(poll_id):
|
||||||
|
c = db()
|
||||||
|
conn = c.cursor()
|
||||||
|
variants = conn.execute(
|
||||||
|
f"SELECT text,number FROM variants WHERE id = '{str(poll_id)}'"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
if variants == []:
|
||||||
|
return '{"error":"Poll with id %s doesn\'t exist"}' % poll_id, 404
|
||||||
|
|
||||||
|
dict = {}
|
||||||
|
for i in variants:
|
||||||
|
dict[i[0]] = i[1]
|
||||||
|
|
||||||
|
url_admin_token = request.args.get('token')
|
||||||
|
header_admin_token = request.headers.get('Authorization')
|
||||||
|
if url_admin_token:
|
||||||
|
admin_token = url_admin_token
|
||||||
|
elif header_admin_token:
|
||||||
|
t = header_admin_token.split("Bearer ")
|
||||||
|
if t[0] != "":
|
||||||
|
admin_token = t[0]
|
||||||
|
else:
|
||||||
|
admin_token = t[1]
|
||||||
|
else:
|
||||||
|
return '{"error":"Unauthorized"}', 401
|
||||||
|
|
||||||
|
db_admin_token = conn.execute("SELECT admin_token FROM polls WHERE id = '%s'" % str(poll_id)).fetchone()[0]
|
||||||
|
if admin_token != db_admin_token:
|
||||||
|
return '{"error":"Unauthorized"}', 401
|
||||||
|
|
||||||
|
poll = conn.execute("SELECT total,type FROM polls WHERE id = '%s'" % poll_id).fetchone()
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM polls WHERE id = '%s'" % poll_id
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM variants WHERE id = '%s'" % poll_id
|
||||||
|
)
|
||||||
|
|
||||||
|
c.commit()
|
||||||
|
c.close()
|
||||||
|
ret = '{"message":"Poll with id %s deleted successfully","results":%s,"total":%s,"type":"%s"}' % (str(poll_id), dict, poll[0], get_poll_type(poll[1]))
|
||||||
|
return ret.replace("\'", "\""), 200
|
||||||
|
|
||||||
|
@app.route("/api/v1/stop/<uuid:poll_id>", methods=["POST", "PUT"])
|
||||||
|
def stoppoll(poll_id):
|
||||||
|
c = db()
|
||||||
|
conn = c.cursor()
|
||||||
|
variants = conn.execute(
|
||||||
|
f"SELECT text,number FROM variants WHERE id = '{str(poll_id)}'"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
if variants == []:
|
||||||
|
return '{"error":"Poll with id %s doesn\'t exist"}' % poll_id, 404
|
||||||
|
|
||||||
|
url_admin_token = request.args.get('token')
|
||||||
|
header_admin_token = request.headers.get('Authorization')
|
||||||
|
if url_admin_token:
|
||||||
|
admin_token = url_admin_token
|
||||||
|
elif header_admin_token:
|
||||||
|
t = header_admin_token.split("Bearer ")
|
||||||
|
if t[0] != "":
|
||||||
|
admin_token = t[0]
|
||||||
|
else:
|
||||||
|
admin_token = t[1]
|
||||||
|
else:
|
||||||
|
return '{"error":"Unauthorized"}', 401
|
||||||
|
|
||||||
|
db_admin_token = conn.execute("SELECT admin_token FROM polls WHERE id = '%s'" % str(poll_id)).fetchone()[0]
|
||||||
|
if admin_token != db_admin_token:
|
||||||
|
return '{"error":"Unauthorized"}', 401
|
||||||
|
|
||||||
|
type = conn.execute("SELECT type FROM polls WHERE id = '%s'" % poll_id).fetchone()[0]
|
||||||
|
if type == 1:
|
||||||
|
new_type = 2
|
||||||
|
elif type == 3:
|
||||||
|
new_type = 9
|
||||||
|
else:
|
||||||
|
return "", 500
|
||||||
|
|
||||||
|
dict = {}
|
||||||
|
for i in variants:
|
||||||
|
dict[i[0]] = i[1]
|
||||||
|
|
||||||
|
conn.execute(f"""
|
||||||
|
UPDATE polls
|
||||||
|
SET type = '{str(new_type)}'
|
||||||
|
WHERE id = '{str(poll_id)}';
|
||||||
|
""")
|
||||||
|
|
||||||
|
poll = conn.execute("SELECT total,type FROM polls WHERE id = '%s'" % poll_id).fetchone()
|
||||||
|
c.commit()
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
ret = '{"message":"Voting %s has ended!","results":%s,"total":%s,"type":"%s"}' % (str(poll_id), dict, poll[0], get_poll_type(poll[1]))
|
||||||
|
return ret.replace("\'", "\""), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/v1/polls/<uuid:poll_id>", methods=["GET"])
|
||||||
|
def getvariants(poll_id):
|
||||||
|
c = db()
|
||||||
|
conn = c.cursor()
|
||||||
|
variants = conn.execute(
|
||||||
|
f"SELECT text,number FROM variants WHERE id = '{str(poll_id)}'"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
arr = []
|
||||||
|
dict = {}
|
||||||
|
for i in variants:
|
||||||
|
arr.append(i[0])
|
||||||
|
dict[i[0]] = i[1]
|
||||||
|
if arr == []:
|
||||||
|
return '{"error":"Poll with id %s doesn\'t exist"}' % poll_id, 404
|
||||||
|
|
||||||
|
poll = conn.execute("SELECT total,type FROM polls WHERE id = '%s'" % poll_id).fetchone()
|
||||||
|
|
||||||
|
c.commit()
|
||||||
|
c.close()
|
||||||
|
ret = '{"variants":%s,"results":%s,"total":%s,"type":"%s"}' % (arr, dict, poll[0], get_poll_type(poll[1]))
|
||||||
|
|
||||||
|
return ret.replace("\'", "\""), 200
|
||||||
|
|
||||||
|
@app.route("/api/v1/polls/<uuid:poll_id>", methods=["POST", "PUT"])
|
||||||
|
def vote(poll_id):
|
||||||
|
if request.cookies.get(str(poll_id)):
|
||||||
|
return '{"error":"Answer has been already sent by you"}', 429
|
||||||
|
|
||||||
|
conn = db()
|
||||||
|
type = conn.execute("SELECT type FROM polls WHERE id = '%s'" % poll_id).fetchone()[0]
|
||||||
|
|
||||||
|
if type == 1:
|
||||||
|
if not request.args.get("id"):
|
||||||
|
return '{"error":"ID param is unset"}', 400
|
||||||
|
ids = [request.args.get("id")]
|
||||||
|
elif type == 3:
|
||||||
|
ids = []
|
||||||
|
if request.args == {}:
|
||||||
|
return '{"error":"IDs params are unset"}', 400
|
||||||
|
for id, check in request.args.items():
|
||||||
|
if check == "true":
|
||||||
|
ids.append(int(id))
|
||||||
|
|
||||||
|
elif type == 2 or type == 9:
|
||||||
|
return '{"error":"The poll with id %s has ended"}' % str(poll_id), 400
|
||||||
|
else:
|
||||||
|
return "", 500
|
||||||
|
|
||||||
|
available_variants = conn.execute(
|
||||||
|
f"SELECT text,number FROM variants WHERE id = '{str(poll_id)}'"
|
||||||
|
).fetchall()
|
||||||
|
available_variants_array = []
|
||||||
|
for i in available_variants:
|
||||||
|
available_variants_array.append(i[0])
|
||||||
|
if available_variants_array == []:
|
||||||
|
return '{"error":"Poll with id %s doesn\'t exist"}' % poll_id, 404
|
||||||
|
|
||||||
|
for varid in ids:
|
||||||
|
varid = int(varid)
|
||||||
|
if varid >= len(available_variants_array):
|
||||||
|
return '{"error:":"Poll %s doesn\'t have variant with id %s"}' % (str(poll_id), str(varid))
|
||||||
|
|
||||||
|
variant = available_variants[varid]
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE variants SET number = number + 1 WHERE id = '%s' and text = '%s';"
|
||||||
|
% (str(poll_id), variant[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE polls SET total = total + 1 WHERE id = '%s'"
|
||||||
|
% str(poll_id)
|
||||||
|
)
|
||||||
|
poll = conn.execute("SELECT total,type FROM polls WHERE id = '%s'" % poll_id).fetchone()
|
||||||
|
|
||||||
|
dict = {}
|
||||||
|
available_variants = conn.execute(
|
||||||
|
f"SELECT text,number FROM variants WHERE id = '{str(poll_id)}'"
|
||||||
|
).fetchall()
|
||||||
|
for i in available_variants:
|
||||||
|
dict[i[0]] = i[1]
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
ret = '{"message":"You have successfully voted in %s!","variants":%s,"result":%s,"total":%s,"type":"%s"}' % (str(poll_id), ids, dict, poll[0], get_poll_type(poll[1]))
|
||||||
|
resp = make_response(ret.replace("\'", "\""))
|
||||||
|
resp.set_cookie(str(poll_id), str(ids))
|
||||||
|
return resp, 200
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
37
src/db.py
Normal file
37
src/db.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import tomli
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
with open("config.toml", mode="rb") as fp:
|
||||||
|
cfg = tomli.load(fp)
|
||||||
|
|
||||||
|
def initdb(conn):
|
||||||
|
c = conn.cursor()
|
||||||
|
if c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='polls';").fetchall() == []:
|
||||||
|
|
||||||
|
print("Creating table 'polls'")
|
||||||
|
c.execute("""
|
||||||
|
CREATE TABLE polls (
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
admin_token TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
creator TEXT,
|
||||||
|
description TEXT,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
alias TEXT,
|
||||||
|
total INTEGER);
|
||||||
|
""")
|
||||||
|
if c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='variants';").fetchall() == []:
|
||||||
|
|
||||||
|
print("Creating table 'variants'")
|
||||||
|
c.execute("""
|
||||||
|
CREATE TABLE variants (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
text TEXT NOT NULL,
|
||||||
|
number INTEGER NOT NULL);
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def db():
|
||||||
|
conn = sqlite3.connect(cfg["database"]["file"])
|
||||||
|
return conn
|
Reference in New Issue
Block a user