Compare commits

..

8 Commits

Author SHA1 Message Date
a99dbe5016 fix linter issues
Some checks failed
continuous-integration/drone/pr Build is failing
2024-03-19 20:51:22 +01:00
8540623961 fix linter issues
Some checks failed
continuous-integration/drone/pr Build is failing
2024-03-19 20:47:09 +01:00
4529b07d54 fix linter issues
Some checks failed
continuous-integration/drone/pr Build is failing
2024-03-19 20:46:22 +01:00
86c2d7e84a fix linter issues
Some checks failed
continuous-integration/drone/pr Build is failing
2024-03-19 20:44:49 +01:00
eeb01bf6e8 fix linter issues
Some checks failed
continuous-integration/drone/pr Build is failing
2024-03-19 20:14:48 +01:00
75085f240c Current status, not everything works
Some checks failed
continuous-integration/drone/pr Build is failing
2024-01-23 20:11:35 +01:00
2c781dec6c Rewrite sqlachemy code for 1.4 to 2.x migration
Some checks failed
continuous-integration/drone/pr Build is failing
2024-01-11 20:48:13 +01:00
ecfa344904 Update dependencies to latest minor version
Some checks failed
continuous-integration/drone/pr Build is failing
werkzeug was added as explicit dependency,
as flask did a wrong pinning >=2.x
This resulted in an installation of werkzeug 3.x
2024-01-10 22:40:19 +01:00
22 changed files with 687 additions and 651 deletions

View File

@ -1,13 +0,0 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
*
!Pipfile
!Pipfile.lock
!data/
!ki/
!LICENSES/
!migrations/
!app.py
!run_prod.py

View File

@ -15,28 +15,15 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: python:3.8.20-alpine@sha256:3d93b1f77efce339aa77db726656872517b0d67837989aa7c4b35bd5ae7e81ba image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2
env:
PYROOT: '/pyroot'
PYTHONUSERBASE: '/pyroot'
commands: commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv verify
- pipenv install --dev - pipenv install --dev
- pipenv run flake8 - pipenv run flake8
- pipenv run reuse lint - pipenv run reuse lint
- pipenv run python -m unittest discover ki - pipenv run python -m unittest discover ki
- name: docker-dry-run
image: plugins/docker:20.18.4@sha256:a8d3d86853c721492213264815f1d00d3ed13f42f5c1855a02f47fa4d5f1e042 image_pull_secrets:
settings: - dockerconfig
registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend
target: ki-backend
dry_run: true
when:
event:
- pull_request
--- ---
kind: pipeline kind: pipeline
@ -54,7 +41,7 @@ depends_on:
steps: steps:
- name: docker-publish - name: docker-publish
image: plugins/docker:20.18.4@sha256:a8d3d86853c721492213264815f1d00d3ed13f42f5c1855a02f47fa4d5f1e042 image: plugins/docker
settings: settings:
registry: git.wtf-eg.de registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend repo: git.wtf-eg.de/kompetenzinventar/backend
@ -81,7 +68,7 @@ depends_on:
steps: steps:
- name: deploy-dev - name: deploy-dev
image: appleboy/drone-ssh:1.7.5@sha256:995677e073454912f26d4c0fdd2f9df2e1f5a30d6603d3f2ece667311b6babb3 image: appleboy/drone-ssh
settings: settings:
host: host:
- dev01.wtf-eg.net - dev01.wtf-eg.net
@ -104,19 +91,14 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: python:3.8.20-alpine@sha256:3d93b1f77efce339aa77db726656872517b0d67837989aa7c4b35bd5ae7e81ba image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2
env:
PYROOT: '/pyroot'
PYTHONUSERBASE: '/pyroot'
commands: commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv install --dev - pipenv install --dev
- pipenv run flake8 - pipenv run flake8
- pipenv run reuse lint - pipenv run reuse lint
- pipenv run python -m unittest discover ki - pipenv run python -m unittest discover ki
- name: docker-publish - name: docker-publish
image: plugins/docker:20.18.4@sha256:a8d3d86853c721492213264815f1d00d3ed13f42f5c1855a02f47fa4d5f1e042 image: plugins/docker
settings: settings:
registry: git.wtf-eg.de registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend repo: git.wtf-eg.de/kompetenzinventar/backend

View File

@ -1 +0,0 @@
3.8.20

12
.reuse/dep5 Normal file
View File

@ -0,0 +1,12 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Kompetenzinventar
Upstream-Contact: Michael Weimann <mail@michael-weimann.eu>
Source: https://git.wtf-eg.de/kompetenzinventar/ki-backend
Files: data/imgs/flags/*
Copyright: 2013 Panayiotis Lipiridis <https://flagicons.lipis.dev/>
License: MIT
Files: Pipfile.lock migrations/*
Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later

View File

@ -2,17 +2,7 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
FROM python:3.8.20-alpine@sha256:3d93b1f77efce339aa77db726656872517b0d67837989aa7c4b35bd5ae7e81ba AS builder FROM git.wtf-eg.de/kompetenzinventar/builder:1.0.2 as builder
ENV PYROOT=/pyroot
ENV PYTHONUSERBASE=$PYROOT
RUN apk add --no-cache \
gcc \
g++ \
musl-dev \
python3-dev && \
pip3 install pipenv
COPY Pipfile* ./ COPY Pipfile* ./
@ -20,10 +10,7 @@ RUN PIP_USER=1 PIP_IGNORE_INSTALLED=1 pipenv install --system --deploy --ignore-
RUN pip3 uninstall --yes pipenv RUN pip3 uninstall --yes pipenv
FROM python:3.8.20-alpine@sha256:3d93b1f77efce339aa77db726656872517b0d67837989aa7c4b35bd5ae7e81ba AS ki-backend FROM git.wtf-eg.de/kompetenzinventar/base:1.0.2 as ki-backend
ENV PYROOT=/pyroot
ENV PYTHONUSERBASE=$PYROOT
# Install six explicitly. Otherwise Python complains about it missing. # Install six explicitly. Otherwise Python complains about it missing.
RUN pip3 install six RUN pip3 install six

34
Pipfile
View File

@ -8,26 +8,26 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
flask = "==2.3.3" flask = "~=3.0.0"
python-dotenv = "==1.0.1" python-dotenv = "~=0.17.1"
flask-migrate = "==4.0.7" flask-migrate = "~=4.0.5"
flask-sqlalchemy = "==2.5.1" flask-sqlalchemy = "~=3.1.1"
sqlalchemy = "==1.4.54" sqlalchemy = "~=2.0.25"
waitress = "==2.1.2" waitress = "~=2.1.2"
pyyaml = "==6.0.2" pyyaml = "~=6.0.1"
flask-cors = "==5.0.0" flask-cors = "~=4.0.0"
ldap3 = "==2.9.1" ldap3 = "~=2.9.1"
pymysql = "==1.1.1" pymysql = "~=1.1.0"
werkzeug = "==2.3.8" werkzeug = "~=3.0.1"
[dev-packages] [dev-packages]
flake8 = "==7.1.1" flake8 = "~=3.9.2"
yapf = "==0.40.2" yapf = "~=0.40.2"
pre-commit = "==2.21.0" pre-commit = "~=2.13.0"
reuse = "==4.0.3" reuse = "~=0.13.0"
[requires] [requires]
python_version = "3.8" python_version = "3.11"
[scripts] [scripts]
clean = "rm data/ki.sqlite" clean = "rm storage/ki.sqlite"

1057
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
# SPDX-FileCopyrightText: NONE
# SPDX-License-Identifier: CC0-1.0
version = 1
SPDX-PackageName = "Kompetenzinventar Backend"
SPDX-PackageDownloadLocation = "https://git.wtf-eg.de/kompetenzinventar/ki-backend"
[[annotations]]
path = "data/imgs/flags/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2013 Panayiotis Lipiridis <https://flagicons.lipis.dev/>"
SPDX-License-Identifier = "MIT"
[[annotations]]
path = ["Pipfile.lock", "migrations/**"]
precedence = "aggregate"
SPDX-FileCopyrightText = "WTF Kooperative eG <https://wtf-eg.de/>"
SPDX-License-Identifier = "AGPL-3.0-or-later"
[[annotations]]
path = ["renovate.json", ".python-version"]
precedence = "aggregate"
SPDX-FileCopyrightText = "WTF Kooperative eG <https://wtf-eg.de/>"
SPDX-License-Identifier = "AGPL-3.0-or-later"

2
app.py
View File

@ -38,7 +38,7 @@ app.config["KI_LDAP_AUTH_PASSWORD"] = os.getenv("KI_LDAP_AUTH_PASSWORD")
app.config["KI_LDAP_BASE_DN"] = os.getenv("KI_LDAP_BASE_DN") app.config["KI_LDAP_BASE_DN"] = os.getenv("KI_LDAP_BASE_DN")
CORS(app) CORS(app)
db = SQLAlchemy(app) db = SQLAlchemy(app, session_options={"future": True})
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")

View File

@ -2,7 +2,7 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
SQLALCHEMY_DATABASE_URI=sqlite:///storage/ki.sqlite SQLALCHEMY_DATABASE_URI=sqlite:///../storage/ki.sqlite
CORS_ORIGINS=* CORS_ORIGINS=*

View File

@ -2,4 +2,5 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
migrations/*.py *
!.gitignore

View File

@ -1,5 +1,4 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
import csv import csv
@ -19,7 +18,7 @@ def seed_contacttypes():
for contacttype in csv_reader: for contacttype in csv_reader:
id = int(contacttype["id"]) id = int(contacttype["id"])
db_contacttype = ContactType.query.get(id) db_contacttype = db.session.get(ContactType, id)
if db_contacttype is None: if db_contacttype is None:
db.session.add(ContactType(id=int(contacttype["id"]), name=contacttype["name"])) db.session.add(ContactType(id=int(contacttype["id"]), name=contacttype["name"]))
@ -99,7 +98,7 @@ def seed(dev: bool):
for skill in skills_csv_reader: for skill in skills_csv_reader:
id = int(skill["id"]) id = int(skill["id"])
db_skill = Skill.query.get(id) db_skill = db.session.get(Skill, id)
if db_skill is None: if db_skill is None:
db.session.add(Skill(id=int(skill["id"]), name=skill["name"])) db.session.add(Skill(id=int(skill["id"]), name=skill["name"]))
@ -113,7 +112,7 @@ def seed(dev: bool):
for iso in iso_csv_reader: for iso in iso_csv_reader:
id = iso["639-1"] id = iso["639-1"]
db_language = Language.query.get(id) db_language = db.session.get(Language, id)
if db_language is None: if db_language is None:
db.session.add(Language(id=iso["639-1"], name=iso["Sprache"])) db.session.add(Language(id=iso["639-1"], name=iso["Sprache"]))
@ -147,12 +146,13 @@ def seed(dev: bool):
freetext="1001010010111!!!", freetext="1001010010111!!!",
skills=[(1, 5)], skills=[(1, 5)],
address=("Friedrich Witzig", "", "", "", "", "", "")) address=("Friedrich Witzig", "", "", "", "", "", ""))
# all_skills = [(skill.id, 3) for skill in Skill.query.all()] # query causes problems
all_skills = [(skill.id, 3) for skill in Skill.query.all()] # seed_user("jutta", languages=[("fr", 5)], skills=all_skills)
seed_user("jutta", languages=[("fr", 5)], skills=all_skills)
seed_user("giesela", skills=[(9, 3), (10, 5)]) seed_user("giesela", skills=[(9, 3), (10, 5)])
seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)]) seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)])
seed_user("monique", languages=[("fr", 4)]) seed_user("monique", languages=[("fr", 4)])
print("seeding done")
db.session.commit() with app.app_context():
db.session.commit() # also problematic
print("commit done")

View File

@ -33,7 +33,7 @@ def update_languages(profile, languages_data):
if "id" not in language_data["language"]: if "id" not in language_data["language"]:
continue continue
language = Language.query.get(language_data["language"]["id"]) language = db.session.get(Language, language_data["language"]["id"])
profile_language = ProfileLanguage.query.filter(ProfileLanguage.profile == profile, profile_language = ProfileLanguage.query.filter(ProfileLanguage.profile == profile,
ProfileLanguage.language == language).first() ProfileLanguage.language == language).first()
@ -110,7 +110,7 @@ def update_contacts(profile, contacts_data):
if "id" in contact_data: if "id" in contact_data:
contact_id = int(contact_data["id"]) contact_id = int(contact_data["id"])
contact_ids_to_be_deleted.remove(contact_id) contact_ids_to_be_deleted.remove(contact_id)
contact = Contact.query.get(contact_id) contact = db.session.get(Contact, contact_id)
else: else:
contact = Contact(profile=profile, contacttype=contacttype) contact = Contact(profile=profile, contacttype=contacttype)
db.session.add(contact) db.session.add(contact)
@ -122,7 +122,7 @@ def update_contacts(profile, contacts_data):
def update_profile(user_id: int): def update_profile(user_id: int):
user = User.query.get(user_id) user = db.session.get(User, user_id)
if user is None: if user is None:
return make_response({}, 404) return make_response({}, 404)

View File

@ -10,7 +10,7 @@ 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, Token, User
from app import app from app import app, db
content_type_svg = "image/svg+xml" content_type_svg = "image/svg+xml"
content_type_png = "image/png" content_type_png = "image/png"
@ -66,7 +66,7 @@ def handle_completion_request(model, key):
def handle_icon_request(model, id, path): def handle_icon_request(model, id, path):
object = model.query.get(id) object = db.session.get(model, id)
if object is None: if object is None:
return make_response({}, 404) return make_response({}, 404)

View File

@ -1,5 +1,4 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
from alembic import command from alembic import command
@ -9,12 +8,14 @@ import unittest
from app import app, db, migrate from app import app, db, migrate
from ki.actions import seed from ki.actions import seed
from ki.models import Skill from ki.models import Skill
from sqlalchemy import select
class ApiTest(unittest.TestCase): class ApiTest(unittest.TestCase):
maxDiff = None maxDiff = None
def setUp(self): def setUp(self):
print("Running setup")
app.debug = True app.debug = True
app.config["KI_AUTH"] = "file" app.config["KI_AUTH"] = "file"
app.config["TESTING"] = True app.config["TESTING"] = True
@ -22,19 +23,38 @@ class ApiTest(unittest.TestCase):
self.client = app.test_client() self.client = app.test_client()
with app.app_context():
config = migrate.get_config() config = migrate.get_config()
with app.app_context():
command.upgrade(config, "head") command.upgrade(config, "head")
seed(True) seed(True)
max_skill = Skill.query.order_by(Skill.id.desc()).first()
# statement = select(Skill).order_by(Skill.id.desc())
# print(statement)
# skill_obj = db.session.scalars(statement).all()
# print(skill_obj)
# statement = select(Skill.id)
# print(statement)
# max_skill = db.session.Skill().order_by(Skill.id.desc()).first()
# max_skill = Skill.query.order_by(Skill.id.desc()).first() # TODO: problematic
with db.session.no_autoflush: # only works on first test run
max_skill = db.session.query(Skill).order_by(Skill.id.desc()).first() # TODO: also problematic,
# skills = db.session.execute(db.select(Skill)).scalars()
# print(max_skill)
# max_skill = db.session.execute(db.select(Skill)
# .order_by(Skill.id.desc())
# ).scalar_one()
print(max_skill)
print("max_skill done")
self.max_skill_id = max_skill.id self.max_skill_id = max_skill.id
def tearDown(self): def tearDown(self):
print("Running teardown")
with app.app_context():
db.drop_all() db.drop_all()
db.engine.dispose() db.engine.dispose()
def login(self, username, password): def login(self, username, password):
# with app.app_context():
login_data = {"username": username, "password": password} login_data = {"username": username, "password": password}
login_response = self.client.post("/users/login", data=json.dumps(login_data), content_type="application/json") login_response = self.client.post("/users/login", data=json.dumps(login_data), content_type="application/json")

View File

@ -1,5 +1,4 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
import unittest import unittest
@ -8,37 +7,39 @@ from ki.test.ApiTest import ApiTest
class TestContactTypesEndpoint(ApiTest): class TestContactTypesEndpoint(ApiTest):
def test_skills_options(self): def test_skills_options(self):
print("test_skills_options")
# with app.app_context():
response = self.client.options("/contacttypes") response = self.client.options("/contacttypes")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("Access-Control-Allow-Origin", response.headers) self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*") self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
def test_get_contacttypes_unauthorised(self): # def test_get_contacttypes_unauthorised(self):
response = self.client.get("/contacttypes?search=m") # print("test_get_contacttypes_unauthorised")
self.assertEqual(response.status_code, 401) # response = self.client.get("/contacttypes?search=m")
# self.assertEqual(response.status_code, 401)
def test_get_contacttypes(self): # def test_get_contacttypes(self):
token = self.login("peter", "geheim")["token"] # token = self.login("peter", "geheim")["token"]
response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token}) # response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token})
self.assertEqual(response.status_code, 200) # self.assertEqual(response.status_code, 200)
self.assertEqual( # self.assertEqual(
{ # {
"contacttypes": [{ # "contacttypes": [{
"id": 5, # "id": 5,
"name": "Mastodon" # "name": "Mastodon"
}, { # }, {
"id": 4, # "id": 4,
"name": "Matrix" # "name": "Matrix"
}, { # }, {
"id": 2, # "id": 2,
"name": "Mobiltelefon" # "name": "Mobiltelefon"
}] # }]
}, response.json) # }, response.json)
self.assertIn("Access-Control-Allow-Origin", response.headers) # self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*") # self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
if __name__ == "main": if __name__ == "main":

View File

@ -8,7 +8,6 @@ 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)

View File

@ -8,7 +8,6 @@ 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)

View File

@ -10,7 +10,6 @@ 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")

View File

@ -8,7 +8,6 @@ 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)

View File

@ -19,7 +19,7 @@ logger = logging.getLogger('alembic.env')
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
config.set_main_option( config.set_main_option(
'sqlalchemy.url', 'sqlalchemy.url',
str(current_app.extensions['migrate'].db.get_engine().url).replace( str(current_app.extensions['migrate'].db.engine.url).replace(
'%', '%%')) '%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata target_metadata = current_app.extensions['migrate'].db.metadata
@ -68,7 +68,7 @@ def run_migrations_online():
directives[:] = [] directives[:] = []
logger.info('No changes in schema detected.') logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.get_engine() connectable = current_app.extensions['migrate'].db.engine
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(

View File

@ -1,18 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
":disableDependencyDashboard",
":maintainLockFilesMonthly",
":pinVersions",
":separateMultipleMajorReleases"
],
"packageRules": [
{
"matchDepNames": ["python"],
"groupName": "Python",
"separateMinorPatch": true,
"separateMultipleMinor": true
}
]
}