Compare commits

..

1 Commits

Author SHA1 Message Date
e60b708ea6 Add preliminary jobs tables into database model 2024-03-05 22:37:43 +00:00
22 changed files with 608 additions and 814 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,18 +15,16 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea 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
image_pull_secrets:
- dockerconfig
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
@ -43,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
@ -70,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
@ -93,19 +91,14 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea 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

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

View File

@ -1 +0,0 @@
3.8.19

View File

@ -10,7 +10,3 @@ License: MIT
Files: Pipfile.lock migrations/* 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 .python-version
Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later

View File

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

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.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea 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.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea 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

29
Pipfile
View File

@ -8,23 +8,22 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
flask = "==2.3.3" flask = "~=2.0.1"
python-dotenv = "==0.21.1" python-dotenv = "~=0.17.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.18"
waitress = "==2.1.2" waitress = "~=2.0.0"
pyyaml = "==6.0.2" pyyaml = "~=6.0.1"
flask-cors = "==3.0.10" flask-cors = "~=3.0.10"
ldap3 = "==2.9.1" ldap3 = "~=2.9"
pymysql = "==1.1.1" pymysql = "~=1.0.2"
werkzeug = "==2.3.8"
[dev-packages] [dev-packages]
flake8 = "==6.1.0" flake8 = "~=3.9.2"
yapf = "==0.40.2" yapf = "~=0.31.0"
pre-commit = "==2.13.0" pre-commit = "~=2.13.0"
reuse = "==0.14.0" reuse = "~=0.13.0"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

1076
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

5
app.py
View File

@ -42,8 +42,5 @@ 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')

View File

@ -19,6 +19,8 @@ class User(db.Model):
tokens = relationship("Token", back_populates="user") tokens = relationship("Token", back_populates="user")
profile = relationship("Profile", back_populates="user") profile = relationship("Profile", back_populates="user")
jobs = relationship("Job", back_populates="owner")
job_responses = relationship("JobResponse",back_populates="user")
def to_dict(self): def to_dict(self):
return {"id": self.id} return {"id": self.id}
@ -33,7 +35,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, nullable=False) availability_status = Column(Boolean, default=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)
@ -146,6 +148,7 @@ class Skill(db.Model):
profiles = relationship("ProfileSkill", back_populates="skill") profiles = relationship("ProfileSkill", back_populates="skill")
searchtopics = relationship("ProfileSearchtopic", back_populates="skill") searchtopics = relationship("ProfileSearchtopic", back_populates="skill")
jobs = relationship("JobSkill", back_populates="skill")
def to_dict(self): def to_dict(self):
return {"id": self.id, "name": self.name, "icon_url": "/skills/{}/icon".format(self.id)} return {"id": self.id, "name": self.name, "icon_url": "/skills/{}/icon".format(self.id)}
@ -228,3 +231,69 @@ class ProfileLanguage(db.Model):
def to_dict(self): def to_dict(self):
return {"profile_id": self.profile_id, "language": self.language.to_dict(), "level": self.level} return {"profile_id": self.profile_id, "language": self.language.to_dict(), "level": self.level}
class Job(db.Model):
__tablename__ = "job"
id = Column(Integer, primary_key=True)
owner_id = Column(Integer, ForeignKey("user.id"), nullable=False)
description = Column(String(5000))
amount_of_people_needed = Column(Integer, nullable=False)
amount_of_weekhours_needed = Column(Integer)
amount_of_payment_hour = Column(Integer)
timeframeImprecise = Column(String(120))
remotePercent = Column(Integer)
location = Column(String(50))
beginDate = Column(DateTime)
endDate = Column(DateTime)
link = Column(String(500))
aidsLevel = Column(Integer) ## Stupid Shit one has to deal with like Teams or weird Apps, Datathrower Webportals...
created = Column(DateTime, nullable=False, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now, nullable=False, default=datetime.now)
withdrawn = Column(DateTime, nullable=True)
owner = relationship("User", back_populates="jobs")
responses = relationship("JobResponse", back_populates="jobs")
skills = relationship("JobSkill", back_populates="job")
def to_dict(self):
return {"job_id": self.id, "owner_id": self.owner_id, "description": self.description,
"amount_of_people_needed": self.amount_of_people_needed, "amount_of_weekhours_needed": self.amount_of_weekhours_needed,
"amount_of_payment_hour": self.amount_of_payment_hour, "timeframeImprecise": self.timeframeImprecise,
"remotePercent": self.timeframeImprecise, "location": self.location, "beginDate": self.beginDate,
"endDate": self.endDate, "link": self.link, "aidsLevel": self.aidsLevel, "created": self.created,
"updated": self.updated, "withdrawn": self.withdrawn
}
class JobSkill(db.Model):
__tablename__ = "job_skill"
job_id = Column(Integer, ForeignKey("job.id"), primary_key=True)
skill_id = Column(Integer, ForeignKey("skill.id"), primary_key=True)
level = Column(SmallInteger, nullable=False)
job = relationship("Job", back_populates="skills")
skill = relationship("Skill", back_populates="jobs")
def to_dict(self):
return {"job_id": self.job_id, "skill": self.skill.to_dict(), "level": self.level}
class JobResponse(db.Model):
__tablename__ = "job_response"
job_id = Column(Integer, ForeignKey("job.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
created = Column(DateTime, nullable=False, default=datetime.now)
withdrawn = Column(DateTime, nullable=True) # no longer interested/available
job = relationship("Job", back_populates="responses")
user = relationship("User", back_populates="job_responses")
def to_dict(self):
return {
"job": self.job.to_dict(),
"user": self.user.to_dict(),
"created": self.created,
"withdrawn": self.withdrawn
}

View File

@ -1,32 +0,0 @@
# 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()

View File

@ -1,28 +0,0 @@
# 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
}

View File

@ -4,18 +4,41 @@
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, User from ki.models import ContactType, Language, Skill, Token, 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 = []

View File

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

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

@ -1,31 +0,0 @@
# 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

View File

@ -1,35 +0,0 @@
"""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 ###

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
}
]
}