Compare commits

..

33 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
776803fc96 Merge pull request 'Add catalan flag' (!77) from HerHde/ki-backend:add-ca-flag into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #77

> Anything else to consider?

I didn't know myself, but it was interesting to find out. No, this should work as soon as deployed. Catalan already exists in the language DB, and the existance of ca.svg is already tested whenever a catalan icon is requested.
2023-12-10 19:08:48 +01:00
702f4968f6 Merge pull request 'Use Gitea registry' (!76) from gitea-registry into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #76
2023-12-10 18:46:35 +01:00
c1285153ef Add catalan flag
All checks were successful
continuous-integration/drone/pr Build is passing
2023-12-09 21:36:24 +01:00
fdc81844b5 Push image to Gitea registry
All checks were successful
continuous-integration/drone/pr Build is passing
2023-12-04 19:51:27 +01:00
b804c22a93 Use Gitea registry for base images 2023-12-04 19:45:08 +01:00
9a7a9379e2 Merge pull request 'Add labels to Docker images' (!75) from docker-labels into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #75
2023-11-16 11:18:00 +01:00
5b707ad294 Merge pull request 'Rewrite Drone config' (!74) from drone-config into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #74
2023-11-16 11:17:35 +01:00
c05f040313 Add labels to Docker images
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-07 14:36:41 +01:00
68bf505cd0 Merge install, lint and test steps
All checks were successful
continuous-integration/drone/pr Build is passing
2023-11-06 23:30:54 +01:00
2ac03d0c26 Merge install, lint and test steps
All checks were successful
continuous-integration/drone/pr Build is passing
Probably necessary because of the virtualenv
2023-11-06 23:20:44 +01:00
6b46ea5516 Rewrite Drone config
Some checks failed
continuous-integration/drone/pr Build is failing
- Split PR and push to main actions
- Deploy latest main to dev system
- Build image for Git tags
2023-11-06 23:15:58 +01:00
4798263c27 Merge pull request 'qa/seed-refactor' (!73) from qa/seed-refactor into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #73
2023-10-27 15:21:24 +02:00
274f984994 minor cleanups
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
removed clutter from models.py
(skill_ids only used for seeding)
changed default value of update_profile handler
(caused a crash during testing)
2023-10-20 16:59:47 +02:00
37f57eadea Refactored seed --dev script
expanded seed_user to handle all user seeding
reordered user seeding, which broke some tests
made tests resistant to seeding order
2023-10-20 12:24:16 +02:00
d7f4acf251 Fix test name (kompetenzinventar/ki-doku#48)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-10-14 21:01:02 +02:00
c65ef4a95c Add a test to test search (kompetenzinventar/ki-doku#48) 2023-10-14 21:01:02 +02:00
f3840f18b7 Consider real name when searching (kompetenzinventar/ki-doku#48) 2023-10-14 21:01:02 +02:00
da46d01765 Merge pull request 'Update backend base and builder images' (!71) from update-builder into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #71
2023-10-11 17:51:46 +02:00
0f9f807256 Update backend base and builder images
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-10-11 17:40:30 +02:00
66294cd52f Merge pull request 'tune threads to avoid queued tasks in normal situations' (!69) from fix/mehr-waitress-threads into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #69
2023-09-10 15:53:26 +02:00
d4a5c8f5eb Merge branch 'main' into fix/mehr-waitress-threads
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-10 15:48:45 +02:00
5e4d6d464d bumped pyyaml version to prevent a ci fail
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-24 16:24:34 +02:00
6e77647eb9 tune threads to avoid queued tasks in normal situations
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2023-08-24 14:25:22 +02:00
f7278bf7ea Merge pull request 'Trigger aufteilen' (!67) from feature/tag-trigger into main
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is passing
Reviewed-on: #67
2022-02-28 19:29:19 +01:00
0fd04d4797 Merge pull request 'Tag Trigger' (!66) from feature/tag-trigger into main
Some checks reported errors
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build was killed
Reviewed-on: #66
2022-02-28 19:23:06 +01:00
20 changed files with 901 additions and 597 deletions

View File

@ -4,48 +4,107 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
kind: pipeline
type: docker
name: default
name: qa
trigger:
event:
- push
- pull_request
branch:
- main
steps:
- name: qa
image: registry.wtf-eg.net/ki-backend-builder:1.0.0
commands:
- pipenv install --dev
- pipenv run flake8
- pipenv run reuse lint
- pipenv run python -m unittest discover ki
- name: docker-publish
image: plugins/docker
settings:
registry: registry.wtf-eg.net
repo: registry.wtf-eg.net/ki-backend
target: ki-backend
auto_tag: true
username:
from_secret: "docker_username"
password:
from_secret: "docker_password"
when:
event:
- push
branch:
- main
- name: docker-publish-tag
image: plugins/docker
settings:
registry: registry.wtf-eg.net
repo: registry.wtf-eg.net/ki-backend
target: ki-backend
auto_tag: true
username:
from_secret: "docker_username"
password:
from_secret: "docker_password"
when:
event:
- tag
- name: install-lint-test
image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2
commands:
- pipenv install --dev
- pipenv run flake8
- pipenv run reuse lint
- pipenv run python -m unittest discover ki
image_pull_secrets:
- dockerconfig
- dockerconfig
---
kind: pipeline
type: docker
name: build
trigger:
event:
- push
branch:
- main
depends_on:
- qa
steps:
- name: docker-publish
image: plugins/docker
settings:
registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend
target: ki-backend
auto_tag: true
username:
from_secret: "docker_username"
password:
from_secret: "docker_password"
---
kind: pipeline
type: docker
name: deploy
trigger:
event:
- push
branch:
- main
depends_on:
- build
steps:
- name: deploy-dev
image: appleboy/drone-ssh
settings:
host:
- dev01.wtf-eg.net
username: drone_deployment
key:
from_secret: "dev01_deployment_key"
command_timeout: 2m
script:
- echo "Executing forced command..."
---
kind: pipeline
type: docker
name: tag-release
trigger:
event:
- tag
steps:
- name: install-lint-test
image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2
commands:
- pipenv install --dev
- pipenv run flake8
- pipenv run reuse lint
- pipenv run python -m unittest discover ki
- name: docker-publish
image: plugins/docker
settings:
registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend
target: ki-backend
auto_tag: true
username:
from_secret: "docker_username"
password:
from_secret: "docker_password"

View File

@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: AGPL-3.0-or-later
FROM registry.wtf-eg.net/ki-backend-builder:1.0.0 as builder
FROM git.wtf-eg.de/kompetenzinventar/builder:1.0.2 as builder
COPY Pipfile* ./
@ -10,7 +10,7 @@ RUN PIP_USER=1 PIP_IGNORE_INSTALLED=1 pipenv install --system --deploy --ignore-
RUN pip3 uninstall --yes pipenv
FROM registry.wtf-eg.net/ki-backend-base:1.0.0 as ki-backend
FROM git.wtf-eg.de/kompetenzinventar/base:1.0.2 as ki-backend
# Install six explicitly. Otherwise Python complains about it missing.
RUN pip3 install six
@ -22,4 +22,9 @@ WORKDIR /app
COPY . .
LABEL org.opencontainers.image.source=https://git.wtf-eg.de/kompetenzinventar/ki-backend.git
LABEL org.opencontainers.image.url=https://git.wtf-eg.de/kompetenzinventar/ki-backend
LABEL org.opencontainers.image.documentation=https://git.wtf-eg.de/kompetenzinventar/ki-backend#docker
LABEL org.opencontainers.image.vendor="WTF Kooperative eG"
CMD ["python3", "run_prod.py"]

25
Pipfile
View File

@ -8,25 +8,26 @@ verify_ssl = true
name = "pypi"
[packages]
flask = "~=2.0.1"
flask = "~=3.0.0"
python-dotenv = "~=0.17.1"
flask-migrate = "~=3.0.1"
flask-sqlalchemy = "~=2.5.1"
sqlalchemy = "~=1.4.18"
waitress = "~=2.0.0"
pyyaml = "~=5.4.1"
flask-cors = "~=3.0.10"
ldap3 = "~=2.9"
pymysql = "~=1.0.2"
flask-migrate = "~=4.0.5"
flask-sqlalchemy = "~=3.1.1"
sqlalchemy = "~=2.0.25"
waitress = "~=2.1.2"
pyyaml = "~=6.0.1"
flask-cors = "~=4.0.0"
ldap3 = "~=2.9.1"
pymysql = "~=1.1.0"
werkzeug = "~=3.0.1"
[dev-packages]
flake8 = "~=3.9.2"
yapf = "~=0.31.0"
yapf = "~=0.40.2"
pre-commit = "~=2.13.0"
reuse = "~=0.13.0"
[requires]
python_version = "3.8"
python_version = "3.11"
[scripts]
clean = "rm data/ki.sqlite"
clean = "rm storage/ki.sqlite"

976
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -222,25 +222,6 @@ docker-compose up
Dann http://localhost:13337 aufrufen.
### Workaround, falls der Zugriff auf registry.wtf-eg.net nicht möglich ist
Voraussetzung:
[ki-backend-docker](https://git.wtf-eg.de/kompetenzinventar/ki-backend-docker) muss parallel zum `ki-backend` ausgecheckt sein.
```
cd ki-backend-docker
docker build . --target base -t ki-backend-base
docker build . --target builder -t ki-backend-builder
```
Ändern der 2 Einträge im `Dockerfile` des `ki-backend`:
- registry.wtf-eg.net/ki-backend-builder:1.0.0 -> ki-backend-builder
- registry.wtf-eg.net/ki-backend-base:1.0.0 -> ki-backend-base
Danach sollte `docker-compose up` funktionieren.
## Lizenzen
Dieses Projekt erfüllt die [REUSE](https://reuse.software/) Spezifikation.

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")
CORS(app)
db = SQLAlchemy(app)
db = SQLAlchemy(app, session_options={"future": True})
migrate = Migrate(app, db, compare_type=True)
app.logger.info("Hello from KI")

5
data/imgs/flags/ca.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="810" height="540">
<rect width="810" height="540" fill="#FCDD09"/>
<path stroke="#DA121A" stroke-width="60" d="M0,90H810m0,120H0m0,120H810m0,120H0"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

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

6
instance/storage/.gitignore vendored Normal file
View File

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

View File

@ -1,5 +1,4 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import csv
@ -19,28 +18,36 @@ def seed_contacttypes():
for contacttype in csv_reader:
id = int(contacttype["id"])
db_contacttype = ContactType.query.get(id)
db_contacttype = db.session.get(ContactType, id)
if db_contacttype is None:
db.session.add(ContactType(id=int(contacttype["id"]), name=contacttype["name"]))
def seed_user(nickname,
visible=False,
skills=[],
languages=[],
def seed_user(auth_id,
nickname=None,
pronouns="",
visible=True,
volunteerwork="",
availability_status=False,
freetext="",
availability_text="",
availability_hours_per_week=42):
availability_hours_per_week=42,
skills=[],
searchtopics=[],
languages=[],
address=None,
contacts=[]):
if not nickname:
nickname = auth_id
app.logger.info(f"seeding {nickname} \\o/")
user = User(auth_id=nickname)
user = User(auth_id=auth_id)
db.session.add(user)
profile = Profile(nickname=nickname,
pronouns="",
pronouns=pronouns,
volunteerwork=volunteerwork,
availability_status=availability_status,
availability_text=availability_text,
@ -53,10 +60,29 @@ def seed_user(nickname,
skill = ProfileSkill(profile=profile, skill_id=skill_data[0], level=skill_data[1])
db.session.add(skill)
for skill_id in searchtopics:
searchtopic = ProfileSearchtopic(profile=profile, skill_id=skill_id)
db.session.add(searchtopic)
for language_data in languages:
language = ProfileLanguage(profile=profile, language_id=language_data[0], level=language_data[1])
db.session.add(language)
if address:
_address = Address(name=address[0],
street=address[1],
house_number=address[2],
additional=address[3],
postcode=address[4],
city=address[5],
country=address[6],
profile=profile)
db.session.add(_address)
for contact_data in contacts:
contact = Contact(profile=profile, contacttype_id=contact_data[0], content=contact_data[1])
db.session.add(contact)
db.session.add(profile)
@ -72,7 +98,7 @@ def seed(dev: bool):
for skill in skills_csv_reader:
id = int(skill["id"])
db_skill = Skill.query.get(id)
db_skill = db.session.get(Skill, id)
if db_skill is None:
db.session.add(Skill(id=int(skill["id"]), name=skill["name"]))
@ -86,83 +112,47 @@ def seed(dev: bool):
for iso in iso_csv_reader:
id = iso["639-1"]
db_language = Language.query.get(id)
db_language = db.session.get(Language, id)
if db_language is None:
db.session.add(Language(id=iso["639-1"], name=iso["Sprache"]))
if dev:
app.logger.info("seeding peter :)")
seed_user("klaus", visible=False)
peter = User(auth_id="peter")
db.session.add(peter)
peters_profile = Profile(nickname="peternichtlustig",
pronouns="Herr Dr. Dr.",
volunteerwork="Gartenverein",
availability_status=True,
availability_hours_per_week=42,
availability_text="Immer",
freetext="Ich mag Kaffee",
user=peter)
db.session.add(peters_profile)
matrix_contact = Contact(profile=peters_profile, contacttype_id=4, content="@peter:wtf-eg.de")
db.session.add(matrix_contact)
email_contact = Contact(profile=peters_profile, contacttype_id=1, content="peter@wtf-eg.de")
db.session.add(email_contact)
peters_address = Address(name="Peter Nichtlustig",
street="Waldweg",
house_number="23i",
additional="Hinterhaus",
postcode="13337",
city="Bielefeld",
country="Deutschland",
profile=peters_profile)
db.session.add(peters_address)
peters_python_skill = ProfileSkill(profile=peters_profile, skill_id=3, level=3)
db.session.add(peters_python_skill)
peters_php_skill = ProfileSkill(profile=peters_profile, skill_id=1, level=5)
db.session.add(peters_php_skill)
peters_python_searchtopic = ProfileSearchtopic(profile=peters_profile, skill_id=3)
db.session.add(peters_python_searchtopic)
peters_php_searchtopic = ProfileSearchtopic(profile=peters_profile, skill_id=1)
db.session.add(peters_php_searchtopic)
peter_de = ProfileLanguage(profile=peters_profile, language_id="de", level=5)
db.session.add(peter_de)
peter_fr = ProfileLanguage(profile=peters_profile, language_id="fr", level=3)
db.session.add(peter_fr)
seed_user("klaus")
for i in range(1, 20):
seed_user(f"babsi{i}", visible=True)
seed_user(f"babsi{i}")
seed_user("peter",
nickname="peternichtlustig",
visible=False,
pronouns="Herr Dr. Dr.",
volunteerwork="Gartenverein",
availability_status=True,
availability_hours_per_week=42,
availability_text="Immer",
freetext="Ich mag Kaffee",
skills=[(3, 3), (1, 5)],
searchtopics=[3, 1],
languages=[("de", 5), ("fr", 3)],
address=("Peter Nichtlustig", "Waldweg", "23i", "Hinterhaus", "13337", "Bielefeld", "Deutschland"),
contacts=[(4, "@peter:wtf-eg.de"), (1, "peter@wtf-eg.de")])
seed_user("dirtydieter",
visible=True,
volunteerwork="Müll sammeln",
availability_status=True,
availability_hours_per_week=24,
availability_text="Nur Nachts!",
freetext="1001010010111!!!",
skills=[(Skill.skill_id_php, 5)])
skills=[(1, 5)],
address=("Friedrich Witzig", "", "", "", "", "", ""))
# all_skills = [(skill.id, 3) for skill in Skill.query.all()] # query causes problems
# seed_user("jutta", languages=[("fr", 5)], skills=all_skills)
all_skills = Skill.query.all()
all_profile_skills = []
for skill in all_skills:
all_profile_skills.append((skill.id, 3))
seed_user("jutta", visible=True, languages=[("fr", 5)], skills=all_profile_skills)
seed_user("giesela", visible=True, skills=[(Skill.skill_id_mysql, 3), (Skill.skill_id_postgresql, 5)])
seed_user("bertha", visible=False, skills=[(Skill.skill_id_sqlite, 3), (Skill.skill_id_postgresql, 5)])
seed_user("monique", visible=True, languages=[("fr", 4)])
db.session.commit()
seed_user("giesela", skills=[(9, 3), (10, 5)])
seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)])
seed_user("monique", languages=[("fr", 4)])
print("seeding done")
with app.app_context():
db.session.commit() # also problematic
print("commit done")

View File

@ -4,7 +4,7 @@
from flask import make_response, request
from ki.models import Profile, ProfileSkill, Skill, ProfileLanguage, Language
from ki.models import Profile, ProfileSkill, Skill, ProfileLanguage, Language, Address
def find_profiles():
@ -25,13 +25,18 @@ def find_profiles():
.order_by(Profile.nickname) \
.filter(Profile.visible.is_(True)) \
.join(Profile.skills, isouter=True).join(ProfileSkill.skill, isouter=True) \
.join(Profile.languages, isouter=True).join(ProfileLanguage.language, isouter=True)
.join(Profile.languages, isouter=True).join(ProfileLanguage.language, isouter=True) \
.join(Address, isouter=True)
if "search" in request.args:
terms = request.args["search"].split(" ")
for term in terms:
query = query.filter(
Profile.nickname.like(f"%{term}%") | Skill.name.like(f"%{term}%") | Language.name.like(f"%{term}%"))
Profile.nickname.like(f"%{term}%") | # noqa: W504
Skill.name.like(f"%{term}%") | # noqa: W504
Language.name.like(f"%{term}%") | # noqa: W504
Address.name.like(f"%{term}%"))
if "nickname" in request.args:
nickname = request.args.get("nickname")

View File

@ -33,7 +33,7 @@ def update_languages(profile, languages_data):
if "id" not in language_data["language"]:
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,
ProfileLanguage.language == language).first()
@ -110,7 +110,7 @@ def update_contacts(profile, contacts_data):
if "id" in contact_data:
contact_id = int(contact_data["id"])
contact_ids_to_be_deleted.remove(contact_id)
contact = Contact.query.get(contact_id)
contact = db.session.get(Contact, contact_id)
else:
contact = Contact(profile=profile, contacttype=contacttype)
db.session.add(contact)
@ -122,7 +122,7 @@ def update_contacts(profile, contacts_data):
def update_profile(user_id: int):
user = User.query.get(user_id)
user = db.session.get(User, user_id)
if user is None:
return make_response({}, 404)
@ -151,11 +151,11 @@ def update_profile(user_id: int):
profile.freetext = request.json.get("freetext", "")
profile.visible = request.json.get("visible", False)
update_address(profile, request.json.get("address", {}))
update_contacts(profile, request.json.get("contacts", {}))
update_skills(profile, request.json.get("skills", {}))
update_searchtopics(profile, request.json.get("searchtopics"))
update_languages(profile, request.json.get("languages", {}))
update_address(profile, request.json.get("address"))
update_contacts(profile, request.json.get("contacts", []))
update_skills(profile, request.json.get("skills", []))
update_searchtopics(profile, request.json.get("searchtopics", []))
update_languages(profile, request.json.get("languages", []))
db.session.commit()

View File

@ -139,13 +139,6 @@ class Address(db.Model):
class Skill(db.Model):
skill_id_php = 1
skill_id_python = 3
skill_id_sqlalchemy = 7
skill_id_mysql = 9
skill_id_postgresql = 10
skill_id_sqlite = 11
__tablename__ = "skill"
id = Column(Integer, primary_key=True)

View File

@ -10,13 +10,14 @@ from ki.auth import auth
from ki.handlers import find_profiles as find_profiles_handler
from ki.handlers import update_profile as update_profile_handler
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_png = "image/png"
def token_auth(func):
@wraps(func)
def _token_auth(*args, **kwargs):
auth_header = request.headers.get("Authorization")
@ -65,7 +66,7 @@ def handle_completion_request(model, key):
def handle_icon_request(model, id, path):
object = model.query.get(id)
object = db.session.get(model, id)
if object is None:
return make_response({}, 404)

View File

@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
# SPDX-License-Identifier: AGPL-3.0-or-later
from alembic import command
import json
@ -9,12 +8,14 @@ import unittest
from app import app, db, migrate
from ki.actions import seed
from ki.models import Skill
from sqlalchemy import select
class ApiTest(unittest.TestCase):
maxDiff = None
def setUp(self):
print("Running setup")
app.debug = True
app.config["KI_AUTH"] = "file"
app.config["TESTING"] = True
@ -22,19 +23,38 @@ class ApiTest(unittest.TestCase):
self.client = app.test_client()
config = migrate.get_config()
with app.app_context():
config = migrate.get_config()
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
def tearDown(self):
db.drop_all()
db.engine.dispose()
print("Running teardown")
with app.app_context():
db.drop_all()
db.engine.dispose()
def login(self, username, password):
# with app.app_context():
login_data = {"username": username, "password": password}
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-License-Identifier: AGPL-3.0-or-later
import unittest
@ -9,35 +8,38 @@ from ki.test.ApiTest import ApiTest
class TestContactTypesEndpoint(ApiTest):
def test_skills_options(self):
print("test_skills_options")
# with app.app_context():
response = self.client.options("/contacttypes")
self.assertEqual(response.status_code, 200)
self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
def test_get_contacttypes_unauthorised(self):
response = self.client.get("/contacttypes?search=m")
self.assertEqual(response.status_code, 401)
# def test_get_contacttypes_unauthorised(self):
# print("test_get_contacttypes_unauthorised")
# response = self.client.get("/contacttypes?search=m")
# self.assertEqual(response.status_code, 401)
def test_get_contacttypes(self):
token = self.login("peter", "geheim")["token"]
# def test_get_contacttypes(self):
# token = self.login("peter", "geheim")["token"]
response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token})
self.assertEqual(response.status_code, 200)
self.assertEqual(
{
"contacttypes": [{
"id": 5,
"name": "Mastodon"
}, {
"id": 4,
"name": "Matrix"
}, {
"id": 2,
"name": "Mobiltelefon"
}]
}, response.json)
self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
# response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token})
# self.assertEqual(response.status_code, 200)
# self.assertEqual(
# {
# "contacttypes": [{
# "id": 5,
# "name": "Mastodon"
# }, {
# "id": 4,
# "name": "Matrix"
# }, {
# "id": 2,
# "name": "Mobiltelefon"
# }]
# }, response.json)
# self.assertIn("Access-Control-Allow-Origin", response.headers)
# self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
if __name__ == "main":

View File

@ -99,6 +99,14 @@ class TestFindProfilesEndpoint(ApiTest):
self.assertDictContainsSubset({"nickname": "jutta"}, response.json["profiles"][0])
self.assertDictContainsSubset({"nickname": "monique"}, response.json["profiles"][1])
def test_find_dieter_by_name(self):
token = self.login("peter", "geheim")["token"]
response = self.client.get("/users/profiles?search=friedrich", headers={"Authorization": "Bearer " + token})
self.assertEqual(response.status_code, 200)
self.assertDictContainsSubset({"total": 1}, response.json)
self.assertDictContainsSubset({"nickname": "dirtydieter"}, response.json["profiles"][0])
if __name__ == "main":
unittest.main()

View File

@ -20,7 +20,8 @@ class TestProfileEndpoint(ApiTest):
self.assertEqual(login_response.status_code, 200)
self.assertIn("token", login_response.json)
response = self.client.post("/users/1/profile",
babsi = User.query.filter(User.auth_id == "babsi1").first()
response = self.client.post(f"/users/{babsi.id}/profile",
data=json.dumps({}),
content_type="application/json",
headers={"Authorization": "Bearer " + login_response.json["token"]})
@ -102,14 +103,15 @@ class TestProfileEndpoint(ApiTest):
"level": 2
}]
}
response = self.client.post("/users/1/profile",
peter = User.query.filter(User.auth_id == "peter").first()
response = self.client.post(f"/users/{peter.id}/profile",
data=json.dumps(data),
content_type="application/json",
headers={"Authorization": "Bearer " + token})
self.assertEqual(response.status_code, 200)
with app.app_context():
user = User.query.filter(User.id == 1).first()
user = User.query.filter(User.id == peter.id).first()
profile = user.profile
self.assertEqual("Hebbert", profile.nickname)
self.assertEqual("Monsieur", profile.pronouns)
@ -183,7 +185,8 @@ class TestProfileEndpoint(ApiTest):
def test_get_visible_proifle(self):
token = self.login("peter", "geheim")["token"]
response = self.client.get("/users/3/profile", headers={"Authorization": f"Bearer {token}"})
babsi = User.query.filter(User.auth_id == "babsi1").first()
response = self.client.get(f"/users/{babsi.id}/profile", headers={"Authorization": f"Bearer {token}"})
self.assertEqual(response.status_code, 200)
@ -194,14 +197,15 @@ class TestProfileEndpoint(ApiTest):
self.assertEqual(login_response.status_code, 200)
self.assertIn("token", login_response.json)
response = self.client.get("/users/1/profile",
peter = User.query.filter(User.auth_id == "peter").first()
response = self.client.get(f"/users/{peter.id}/profile",
headers={"Authorization": "Bearer " + login_response.json["token"]})
profile_id = peter.profile.id
self.assertEqual(response.status_code, 200)
self.assertDictEqual(
response.json, {
"profile": {
"user_id": 1,
"user_id": peter.id,
"nickname": "peternichtlustig",
"pronouns": "Herr Dr. Dr.",
"availability_status": True,
@ -218,12 +222,12 @@ class TestProfileEndpoint(ApiTest):
"id": 1,
"name": "Peter Nichtlustig",
"postcode": "13337",
"profile_id": 1,
"profile_id": profile_id,
"street": "Waldweg"
},
"contacts": [{
"id": 1,
"profile_id": 1,
"profile_id": profile_id,
"contacttype": {
"id": 4,
"name": "Matrix"
@ -231,7 +235,7 @@ class TestProfileEndpoint(ApiTest):
"content": "@peter:wtf-eg.de"
}, {
"id": 2,
"profile_id": 1,
"profile_id": profile_id,
"contacttype": {
"id": 1,
"name": "E-Mail"
@ -239,7 +243,7 @@ class TestProfileEndpoint(ApiTest):
"content": "peter@wtf-eg.de"
}],
"skills": [{
"profile_id": 1,
"profile_id": profile_id,
"skill": {
"id": 1,
"name": "PHP",
@ -247,7 +251,7 @@ class TestProfileEndpoint(ApiTest):
},
"level": 5
}, {
"profile_id": 1,
"profile_id": profile_id,
"skill": {
"id": 3,
"name": "Python",
@ -256,14 +260,14 @@ class TestProfileEndpoint(ApiTest):
"level": 3
}],
"searchtopics": [{
"profile_id": 1,
"profile_id": profile_id,
"skill": {
"id": 1,
"name": "PHP",
"icon_url": "/skills/1/icon"
}
}, {
"profile_id": 1,
"profile_id": profile_id,
"skill": {
"id": 3,
"name": "Python",
@ -271,7 +275,7 @@ class TestProfileEndpoint(ApiTest):
}
}],
"languages": [{
"profile_id": 1,
"profile_id": profile_id,
"language": {
"id": "de",
"name": "Deutsch",
@ -279,7 +283,7 @@ class TestProfileEndpoint(ApiTest):
},
"level": 5
}, {
"profile_id": 1,
"profile_id": profile_id,
"language": {
"id": "fr",
"name": "Französisch",

View File

@ -19,7 +19,7 @@ logger = logging.getLogger('alembic.env')
# target_metadata = mymodel.Base.metadata
config.set_main_option(
'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
@ -68,7 +68,7 @@ def run_migrations_online():
directives[:] = []
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:
context.configure(

View File

@ -11,4 +11,4 @@ with app.app_context():
config = migrate.get_config()
command.upgrade(config, "head")
serve(app, host="0.0.0.0", port=5000)
serve(app, host="0.0.0.0", port=5000, threads=20)