-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from flask import Flask | ||
import os | ||
|
||
app_path = os.path.dirname(__file__) | ||
app = Flask(__name__, root_path=app_path, instance_path=os.path.join(app_path, "instance"), instance_relative_config=True) | ||
|
||
app.config.from_pyfile("config.py") | ||
|
||
app.app_context() | ||
|
||
with app.app_context(): | ||
from . import database | ||
database.init() | ||
|
||
from .controller import build_number |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
from flask import Flask, jsonify, current_app, request | ||
from build_number_generator.database import get_database | ||
|
||
import hashlib | ||
|
||
def hash_token(token: str) -> str: | ||
digest = hashlib.blake2b(digest_size=64) | ||
digest.update(str.encode(token)) | ||
return digest.hexdigest() | ||
|
||
|
||
def fail(status_message: str, status_code: int = 400) -> tuple: | ||
response = {} | ||
response["status"] = status_code | ||
response["message"] = status_message | ||
return response, status_code | ||
|
||
|
||
@current_app.errorhandler(405) | ||
def method_not_allowed(e) -> tuple: | ||
return fail("Method not allowed", 405) | ||
|
||
|
||
@current_app.route("/build-number", methods=["POST"]) | ||
@current_app.route("/build-number/<string:series>", methods=["POST"]) | ||
@current_app.route("/build-number/<string:series>/<string:commit>", methods=["POST"]) | ||
def build_number(series=None, commit=None): | ||
|
||
token = request.form.get("token") | ||
|
||
if not token: | ||
return fail("Unautherized", 401) | ||
|
||
if not hash_token(token) in current_app.config["AUTHORIZED_HASHES"]: | ||
return fail("Unautherized", 401) | ||
|
||
if not series: | ||
return fail("Required parameter not provided: series") | ||
|
||
if series.count(".") != 1: | ||
return fail("Invalid parameter: series. Format: <major>.<minor>") | ||
|
||
major, minor = series.split(".") | ||
|
||
if not major.isnumeric() or not minor.isnumeric(): | ||
return fail("Invalid parameter: series. Format: <major>.<minor>") | ||
|
||
major = int(major) | ||
minor = int(minor) | ||
|
||
if not commit: | ||
return fail("Required parameter not provided: commit hash") | ||
|
||
if len(commit) < 8: | ||
return fail("Invalid parameter: commit") | ||
|
||
response = {} | ||
|
||
db = get_database() | ||
|
||
# Find or insert series | ||
result = db.execute("SELECT * FROM series WHERE name='%i.%i'" % (major, minor)) | ||
row = result.fetchone() | ||
|
||
if row is not None: | ||
series_id = row["id"] | ||
else: | ||
cursor = db.execute("INSERT INTO series VALUES (NULL, '%i.%i', NULL)" % (major, minor)) | ||
series_id = cursor.lastrowid | ||
|
||
# Find or insert build number | ||
result = db.execute("SELECT * FROM build WHERE series_id=%i AND commit_hash='%s'" % (series_id, commit)) | ||
row = result.fetchone() | ||
|
||
if row is not None: | ||
build_number = row["build_number"] | ||
|
||
response["message"] = "Known commit" | ||
else: | ||
result = db.execute("SELECT build_number FROM build WHERE series_id=%i ORDER BY build_number DESC LIMIT 1" % (series_id)) | ||
row = result.fetchone() | ||
build_number = (row["build_number"] + 1) if row is not None else 1 | ||
cursor = db.execute("INSERT INTO build VALUES (NULL, %i, '%s', %i, NULL)" % (series_id, commit, build_number)) | ||
|
||
response["message"] = "New build number created" | ||
|
||
db.commit() | ||
|
||
response["stauts"] = 200 | ||
response["commit_hash"] = commit | ||
response["series"] = "%i.%i" % (major, minor) | ||
response["series_id"] = series_id | ||
response["build_number"] = build_number | ||
|
||
return response, 200 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import sqlite3 | ||
|
||
import click | ||
from flask import current_app, g | ||
|
||
|
||
def get_database() -> sqlite3.Connection: | ||
"""Gets the database connection for the current request. If this function is called for the first time for the given request, | ||
this function also connects to the database, whereas later calls will simply reuse that connection""" | ||
if "database" not in g: | ||
g.database = sqlite3.connect( | ||
current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES | ||
) | ||
g.database.row_factory = sqlite3.Row | ||
|
||
return g.database | ||
|
||
|
||
def close_database(_ = None) -> None: | ||
"""If a connection to the database has been established, this function closes it again""" | ||
database: sqlite3.Connection = g.pop("database", None) | ||
|
||
if database is not None: | ||
database.close() | ||
|
||
|
||
def init_database() -> None: | ||
"""This function (re)creates the database from scratch. If the database existed | ||
before, its contents will be dropped""" | ||
database: sqlite3.Connection = get_database() | ||
|
||
with current_app.open_resource("schema.sql") as file: | ||
database.executescript(file.read().decode("utf-8")) | ||
|
||
|
||
@click.command("init-db") | ||
def init_db_command() -> None: | ||
"""Clear existing data and create new tables.""" | ||
init_database() | ||
click.echo("Database initialized") | ||
|
||
|
||
def init() -> None: | ||
current_app.teardown_appcontext(close_database) | ||
current_app.cli.add_command(init_db_command) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
DROP TABLE IF EXISTS series; | ||
DROP TABLE IF EXISTS build; | ||
|
||
CREATE TABLE series ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
name TEXT NOT NULL, | ||
created_on DEFAULT CURRENT_TIMESTAMP | ||
); | ||
|
||
CREATE TABLE build ( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
series_id INTEGER NOT NULL, | ||
commit_hash TEXT NOT NULL, | ||
build_number INTEGER NOT NULL, | ||
created_on DEFAULT CURRENT_TIMESTAMP, | ||
UNIQUE(series_id, commit_hash), | ||
FOREIGN KEY (series_id) REFERENCES series (id) | ||
); |