Compare commits

...

23 Commits
v1.1.0 ... main

Author SHA1 Message Date
jaschop 776803fc96 Merge pull request 'Add catalan flag' (!77) from HerHde/ki-backend:add-ca-flag into main
continuous-integration/drone/push Build is passing Details
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
jaschop 702f4968f6 Merge pull request 'Use Gitea registry' (!76) from gitea-registry into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #76
2023-12-10 18:46:35 +01:00
HerHde c1285153ef
Add catalan flag
continuous-integration/drone/pr Build is passing Details
2023-12-09 21:36:24 +01:00
Brain fdc81844b5
Push image to Gitea registry
continuous-integration/drone/pr Build is passing Details
2023-12-04 19:51:27 +01:00
Brain b804c22a93
Use Gitea registry for base images 2023-12-04 19:45:08 +01:00
jaschop 9a7a9379e2 Merge pull request 'Add labels to Docker images' (!75) from docker-labels into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #75
2023-11-16 11:18:00 +01:00
jaschop 5b707ad294 Merge pull request 'Rewrite Drone config' (!74) from drone-config into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #74
2023-11-16 11:17:35 +01:00
Brain c05f040313
Add labels to Docker images
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2023-11-07 14:36:41 +01:00
Brain 68bf505cd0
Merge install, lint and test steps
continuous-integration/drone/pr Build is passing Details
2023-11-06 23:30:54 +01:00
Brain 2ac03d0c26
Merge install, lint and test steps
continuous-integration/drone/pr Build is passing Details
Probably necessary because of the virtualenv
2023-11-06 23:20:44 +01:00
Brain 6b46ea5516
Rewrite Drone config
continuous-integration/drone/pr Build is failing Details
- 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
jaschop 4798263c27 Merge pull request 'qa/seed-refactor' (!73) from qa/seed-refactor into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #73
2023-10-27 15:21:24 +02:00
jaschop 274f984994
minor cleanups
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
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
jaschop 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
zeitschlag d7f4acf251 Fix test name (kompetenzinventar/ki-doku#48)
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-10-14 21:01:02 +02:00
zeitschlag c65ef4a95c Add a test to test search (kompetenzinventar/ki-doku#48) 2023-10-14 21:01:02 +02:00
zeitschlag f3840f18b7 Consider real name when searching (kompetenzinventar/ki-doku#48) 2023-10-14 21:01:02 +02:00
jaschop da46d01765 Merge pull request 'Update backend base and builder images' (!71) from update-builder into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #71
2023-10-11 17:51:46 +02:00
Brain 0f9f807256
Update backend base and builder images
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2023-10-11 17:40:30 +02:00
jaschop 66294cd52f Merge pull request 'tune threads to avoid queued tasks in normal situations' (!69) from fix/mehr-waitress-threads into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #69
2023-09-10 15:53:26 +02:00
jaschop d4a5c8f5eb
Merge branch 'main' into fix/mehr-waitress-threads
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-09-10 15:48:45 +02:00
jaschop 5e4d6d464d
bumped pyyaml version to prevent a ci fail
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-08-24 16:24:34 +02:00
jaschop 6e77647eb9
tune threads to avoid queued tasks in normal situations
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is failing Details
2023-08-24 14:25:22 +02:00
13 changed files with 721 additions and 515 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"]

View File

@ -14,7 +14,7 @@ flask-migrate = "~=3.0.1"
flask-sqlalchemy = "~=2.5.1"
sqlalchemy = "~=1.4.18"
waitress = "~=2.0.0"
pyyaml = "~=5.4.1"
pyyaml = "~=6.0.1"
flask-cors = "~=3.0.10"
ldap3 = "~=2.9"
pymysql = "~=1.0.2"

860
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.

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

@ -25,22 +25,30 @@ def seed_contacttypes():
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 +61,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)
@ -92,77 +119,40 @@ def seed(dev: bool):
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.query.all()
all_profile_skills = []
for skill in all_skills:
all_profile_skills.append((skill.id, 3))
all_skills = [(skill.id, 3) for skill in Skill.query.all()]
seed_user("jutta", languages=[("fr", 5)], skills=all_skills)
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)])
seed_user("giesela", skills=[(9, 3), (10, 5)])
seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)])
seed_user("monique", languages=[("fr", 4)])
db.session.commit()

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

@ -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

@ -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

@ -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)