Compare commits

...

19 Commits

Author SHA1 Message Date
Gulliver
c4f5979d95 added model and migration for resume 2024-08-30 19:13:43 +02:00
Gulliver
92f5393a4c added nullable false to prevent unexpected migration of this table 2024-08-30 19:11:03 +02:00
Gulliver
383ef8b512 initial (empty) blueprint and model 2024-08-30 19:11:03 +02:00
033dee7836 Merge pull request 'Update dependency python-dotenv to v0.21.1' () from renovate/python-dotenv-0.x into main
Reviewed-on: 
2024-08-28 16:27:46 +02:00
ca81e8bf70
Merge branch 'main' into renovate/python-dotenv-0.x 2024-08-28 16:02:01 +02:00
d507a20a93 Merge pull request 'Update dependency flake8 to v6' () from renovate/flake8-6.x into main
Reviewed-on: 
2024-08-28 15:52:02 +02:00
3f2c23c386
Merge branch 'main' into renovate/flake8-6.x 2024-08-28 15:46:31 +02:00
b46ac5e379 Merge pull request 'Update dependency pymysql to v1.1.1' () from renovate/pymysql-1.x into main
Reviewed-on: 
2024-08-28 15:43:17 +02:00
fa4429b6ef Update dependency flake8 to v6 2024-08-28 13:37:17 +00:00
7a0f2434db Update dependency python-dotenv to v0.21.1 2024-08-28 13:36:27 +00:00
8c3fe3fe7d Update dependency pymysql to v1.1.1 2024-08-28 13:36:11 +00:00
56ade6de68 Merge pull request 'Update dependency flask to v2.3.3' () from renovate/flask-2.x into main
Reviewed-on: 
Reviewed-by: Brain <brain@noreply.git.wtf-eg.de>
2024-08-28 15:19:36 +02:00
28cf714217 Update dependency flask to v2.3.3 2024-08-28 12:35:38 +00:00
9ff56f6676 Merge pull request 'Update dependency yapf to v0.40.2' () from renovate/yapf-0.x into main
Reviewed-on: 
2024-08-28 13:46:17 +02:00
2412df4960
Apply yapf and add ignorefile 2024-08-28 13:43:33 +02:00
469ef511d6 Merge pull request 'Add .python-version file' () from pyenv into main
Reviewed-on: 
2024-08-28 13:19:28 +02:00
47d2c94b79
Specify license for .python-version 2024-08-28 12:57:31 +02:00
384dd82454 Update dependency yapf to v0.40.2 2024-08-27 17:38:01 +00:00
b0dcfacd25
Add .python-version file 2024-08-27 19:08:52 +02:00
18 changed files with 226 additions and 74 deletions

@ -27,5 +27,5 @@ repos:
name: reuse name: reuse
entry: reuse lint entry: reuse lint
language: system language: system
exclude: .* exclude: ^(venv).*$
always_run: true always_run: true

1
.python-version Normal file

@ -0,0 +1 @@
3.8.19

@ -11,6 +11,6 @@ Files: Pipfile.lock migrations/*
Copyright: WTF Kooperative eG <https://wtf-eg.de/> Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later License: AGPL-3.0-or-later
Files: renovate.json Files: renovate.json .python-version
Copyright: WTF Kooperative eG <https://wtf-eg.de/> Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later License: AGPL-3.0-or-later

5
.yapfignore Normal file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
migrations/*.py

10
Pipfile

@ -8,8 +8,8 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
flask = "==2.0.3" flask = "==2.3.3"
python-dotenv = "==0.17.1" python-dotenv = "==0.21.1"
flask-migrate = "==3.0.1" flask-migrate = "==3.0.1"
flask-sqlalchemy = "==2.5.1" flask-sqlalchemy = "==2.5.1"
sqlalchemy = "==1.4.53" sqlalchemy = "==1.4.53"
@ -17,12 +17,12 @@ waitress = "==2.1.2"
pyyaml = "==6.0.2" pyyaml = "==6.0.2"
flask-cors = "==3.0.10" flask-cors = "==3.0.10"
ldap3 = "==2.9.1" ldap3 = "==2.9.1"
pymysql = "==1.0.3" pymysql = "==1.1.1"
werkzeug = "==2.3.8" werkzeug = "==2.3.8"
[dev-packages] [dev-packages]
flake8 = "==3.9.2" flake8 = "==6.1.0"
yapf = "==0.31.0" yapf = "==0.40.2"
pre-commit = "==2.13.0" pre-commit = "==2.13.0"
reuse = "==0.14.0" reuse = "==0.14.0"

115
Pipfile.lock generated

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "3e2ddadb687d67eee6974c15de24ee02b77941eae9901b4939249f7e2881110e" "sha256": "701ece99277177da8e5adf857508e3b83e00e1f75bd20509b370bbe0bd8c008f"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -24,6 +24,14 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==1.13.2" "version": "==1.13.2"
}, },
"blinker": {
"hashes": [
"sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01",
"sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"
],
"markers": "python_version >= '3.8'",
"version": "==1.8.2"
},
"click": { "click": {
"hashes": [ "hashes": [
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
@ -34,12 +42,12 @@
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f", "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc",
"sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d" "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.8'",
"version": "==2.0.3" "version": "==2.3.3"
}, },
"flask-cors": { "flask-cors": {
"hashes": [ "hashes": [
@ -135,7 +143,7 @@
"sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1",
"sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5" "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"
], ],
"markers": "python_version < '3.9'", "markers": "python_version < '3.10'",
"version": "==8.4.0" "version": "==8.4.0"
}, },
"importlib-resources": { "importlib-resources": {
@ -257,20 +265,21 @@
}, },
"pymysql": { "pymysql": {
"hashes": [ "hashes": [
"sha256:3dda943ef3694068a75d69d071755dbecacee1adf9a1fc5b206830d2b67d25e8", "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c",
"sha256:89fc6ae41c0aeb6e1f7710cdd623702ea2c54d040565767a78b00a5ebb12f4e5" "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.0.3" "version": "==1.1.1"
}, },
"python-dotenv": { "python-dotenv": {
"hashes": [ "hashes": [
"sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544", "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49",
"sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f" "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.17.1" "markers": "python_version >= '3.7'",
"version": "==0.21.1"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@ -419,11 +428,11 @@
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [
"sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31", "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064",
"sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d" "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==3.20.0" "version": "==3.20.1"
} }
}, },
"develop": { "develop": {
@ -578,12 +587,12 @@
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23",
"sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907" "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "markers": "python_full_version >= '3.8.1'",
"version": "==3.9.2" "version": "==6.1.0"
}, },
"identify": { "identify": {
"hashes": [ "hashes": [
@ -595,11 +604,19 @@
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac",
"sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"
], ],
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.6'",
"version": "==3.7" "version": "==3.8"
},
"importlib-metadata": {
"hashes": [
"sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1",
"sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"
],
"markers": "python_version < '3.10'",
"version": "==8.4.0"
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
@ -685,10 +702,11 @@
}, },
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
], ],
"version": "==0.6.1" "markers": "python_version >= '3.6'",
"version": "==0.7.0"
}, },
"nodeenv": { "nodeenv": {
"hashes": [ "hashes": [
@ -717,19 +735,19 @@
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f",
"sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '3.8'",
"version": "==2.7.0" "version": "==2.11.1"
}, },
"pyflakes": { "pyflakes": {
"hashes": [ "hashes": [
"sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774",
"sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '3.8'",
"version": "==2.3.1" "version": "==3.1.0"
}, },
"python-debian": { "python-debian": {
"hashes": [ "hashes": [
@ -819,11 +837,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e", "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f",
"sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193" "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e"
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==73.0.1" "version": "==74.0.0"
}, },
"toml": { "toml": {
"hashes": [ "hashes": [
@ -833,6 +851,14 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2" "version": "==0.10.2"
}, },
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.1"
},
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
@ -851,11 +877,20 @@
}, },
"yapf": { "yapf": {
"hashes": [ "hashes": [
"sha256:408fb9a2b254c302f49db83c59f9aa0b4b0fd0ec25be3a5c51181327922ff63d", "sha256:4dab8a5ed7134e26d57c1647c7483afb3f136878b579062b786c9ba16b94637b",
"sha256:e3a234ba8455fe201eaa649cdac872d590089a18b661e39bbac7020978dd9c2e" "sha256:adc8b5dd02c0143108878c499284205adb258aad6db6634e5b869e7ee2bd548b"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.31.0" "markers": "python_version >= '3.7'",
"version": "==0.40.2"
},
"zipp": {
"hashes": [
"sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064",
"sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"
],
"markers": "python_version >= '3.8'",
"version": "==3.20.1"
} }
} }
} }

5
app.py

@ -42,5 +42,8 @@ db = SQLAlchemy(app)
migrate = Migrate(app, db, compare_type=True) migrate = Migrate(app, db, compare_type=True)
app.logger.info("Hello from KI") app.logger.info("Hello from KI")
from ki import module # noqa from ki import module # noqa
from ki import resume
app.register_blueprint(resume.bp_resume, url_prefix='/resume')

@ -33,7 +33,7 @@ class Profile(db.Model):
volunteerwork = Column(String(4000), default="") volunteerwork = Column(String(4000), default="")
freetext = Column(String(4000), default="") freetext = Column(String(4000), default="")
availability_status = Column(Boolean, default=False) availability_status = Column(Boolean, default=False, nullable=False)
availability_text = Column(String(4000), default="") availability_text = Column(String(4000), default="")
availability_hours_per_week = Column(Integer, default=0) availability_hours_per_week = Column(Integer, default=0)

32
ki/resume.py Normal file

@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
from flask import Blueprint
from ki.token_auth import token_auth
from ki.resume_models import Resume
bp_resume = Blueprint('resume', __name__,
template_folder='templates')
@bp_resume.route('/')
@token_auth
def show(page):
"""
return the list of resumes as object with data array inside
"""
pass
@bp_resume.route("/<resume_id>")
@token_auth
def get_resume(resume_id):
"""
lookup for resume with resume_id, check if its from this user
and provide its contents in the appropriate format
shall support 'format' parameter with values of 'html', 'pdf'
if no parameter is given, json is returned
"""
r = Resume()
return r.to_dict()

28
ki/resume_models.py Normal file

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
from sqlalchemy import Column, Integer, String, ForeignKey, JSON
from sqlalchemy.orm import relationship
from app import db
class Resume(db.Model):
__tablename__ = 'resume'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("user.id", ondelete='CASCADE'))
label = Column("label", String(50), nullable=True)
data = Column('data', JSON)
user = relationship("User", backref='user', passive_deletes=True)
def to_dict(self):
return {
"id": self.id,
'user_id': self.user_id,
"label": self.label,
"data": self.data
}

@ -4,41 +4,18 @@
import os import os
from flask import g, make_response, request, send_file from flask import g, make_response, request, send_file
from functools import wraps
from ki.auth import auth from ki.auth import auth
from ki.handlers import find_profiles as find_profiles_handler from ki.handlers import find_profiles as find_profiles_handler
from ki.handlers import update_profile as update_profile_handler from ki.handlers import update_profile as update_profile_handler
from ki.models import ContactType, Language, Skill, Token, User from ki.models import ContactType, Language, Skill, User
from app import app from app import app
from ki.token_auth import token_auth
content_type_svg = "image/svg+xml" content_type_svg = "image/svg+xml"
content_type_png = "image/png" content_type_png = "image/png"
def token_auth(func):
@wraps(func)
def _token_auth(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if (auth_header is None):
return make_response({}, 401)
if not auth_header.startswith("Bearer"):
return make_response({}, 401)
token = Token.query.filter(Token.token == auth_header[7:]).first()
if token is None:
return make_response({}, 403)
g.user = token.user
return func(*args, **kwargs)
return _token_auth
def models_to_list(models): def models_to_list(models):
models_list = [] models_list = []

@ -8,6 +8,7 @@ from ki.test.ApiTest import ApiTest
class TestContactTypesEndpoint(ApiTest): class TestContactTypesEndpoint(ApiTest):
def test_skills_options(self): def test_skills_options(self):
response = self.client.options("/contacttypes") response = self.client.options("/contacttypes")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

@ -8,6 +8,7 @@ from ki.test.ApiTest import ApiTest
class TestFindProfilesEndpoint(ApiTest): class TestFindProfilesEndpoint(ApiTest):
def test_find_profiles_options(self): def test_find_profiles_options(self):
response = self.client.options("/users/profiles") response = self.client.options("/users/profiles")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

@ -8,6 +8,7 @@ from ki.test.ApiTest import ApiTest
class TestLanguagesEndpoint(ApiTest): class TestLanguagesEndpoint(ApiTest):
def test_skills_options(self): def test_skills_options(self):
response = self.client.options("/languages") response = self.client.options("/languages")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

@ -10,6 +10,7 @@ from ki.test.ApiTest import ApiTest
class TestLoginEndpoint(ApiTest): class TestLoginEndpoint(ApiTest):
def test_login(self): def test_login(self):
response1_data = self.login("peter", "geheim") response1_data = self.login("peter", "geheim")
response2_data = self.login("peter", "geheim") response2_data = self.login("peter", "geheim")

@ -8,6 +8,7 @@ from ki.test.ApiTest import ApiTest
class TestSkillsEndpoint(ApiTest): class TestSkillsEndpoint(ApiTest):
def test_skills_options(self): def test_skills_options(self):
response = self.client.options("/skills") response = self.client.options("/skills")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

31
ki/token_auth.py Normal file

@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
from flask import g, make_response, request
from functools import wraps
from ki.models import Token
def token_auth(func):
@wraps(func)
def _token_auth(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if (auth_header is None):
return make_response({}, 401)
if not auth_header.startswith("Bearer"):
return make_response({}, 401)
token = Token.query.filter(Token.token == auth_header[7:]).first()
if token is None:
return make_response({}, 403)
g.user = token.user
return func(*args, **kwargs)
return _token_auth

@ -0,0 +1,35 @@
"""add resume
Revision ID: 6be5073423b4
Revises: b5023977cbda
Create Date: 2024-08-30 18:18:14.555874
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6be5073423b4'
down_revision = 'b5023977cbda'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('resume',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('label', sa.String(length=50), nullable=True),
sa.Column('data', sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('resume')
# ### end Alembic commands ###