Compare commits

..

4 Commits

7 changed files with 254 additions and 79 deletions

View File

@ -74,6 +74,16 @@ curl -s \
http://localhost:5000/users/login | jq
```
```
curl -s \
-D "/dev/stderr" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 22e6c5fc-8a5a-440e-b1f4-018deb9fd24e" \
-d '{"pronouns": "Herr Dr. Dr."}' \
http://localhost:5000/users/1/profile | jq
```
```
curl -s \
-D "/dev/stderr" \

View File

@ -5,28 +5,41 @@ from app import app, db
from ki.models import User, Token
class UserWrongCredentialsException(Exception):
pass
class UserAllreadyLoggedInException(Exception):
pass
def auth(username, password):
auth_file_path = app.config["KI_DATA_DIR"] + "/auth.yml"
with open(auth_file_path, "r") as auth_file_stream:
users = yaml.safe_load(auth_file_stream)
if username not in users:
try:
users = yaml.safe_load(auth_file_stream)
except yaml.YAMLError:
print('Could not parse auth.yml.')
try:
auth_user = users[username]
if auth_user["password"] != password:
raise UserWrongCredentialsException
except (UserWrongCredentialsException, KeyError):
print('Wrong username/password combination')
return None
auth_user = users[username]
else:
user = User.query.filter(User.auth_id.__eq__(username)).first()
if auth_user["password"] != password:
return None
token = Token(token=str(uuid.uuid4()), user=user)
user = User.query.filter(User.auth_id.__eq__(username)).first()
db.session.add(token)
db.session.commit()
if user is None:
user = User(nickname=username, auth_id=username)
db.session.add(user)
token = Token(token=str(uuid.uuid4()), user=user)
db.session.add(token)
db.session.commit()
return token
return token

View File

@ -9,25 +9,43 @@ from app import db
class User(db.Model):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
auth_id = Column(String(50), nullable=False, unique=True)
profile_id = Column(Integer, ForeignKey("profile.id"), nullable=True)
tokens = relationship("Token", uselist=False, back_populates="user")
profile = relationship("Profile", back_populates="user")
def to_dict(self):
return {"id": self.id}
class Profile(db.Model):
__tablename__ = "profile"
id = Column(Integer, primary_key=True)
nickname = Column(String(25), unique=True, nullable=False)
pronouns = Column(String(25), default="")
volunteerwork = Column(String(4000), default="")
freetext = Column(String(4000), default="")
created = Column(DateTime, nullable=False, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now, nullable=False, default=datetime.now)
auth_id = Column(String(50), nullable=False, unique=True)
updated = Column(DateTime,
onupdate=datetime.now,
nullable=False,
default=datetime.now)
user = relationship("User", back_populates="profile", uselist=False)
contacts = relationship("Contact")
address = relationship("Address", uselist=False, back_populates="user")
tokens = relationship("Token", uselist=False, back_populates="user")
skills = relationship("UserSkill", back_populates="user")
languages = relationship("UserLanguage", back_populates="user")
address = relationship("Address", uselist=False, back_populates="profile")
skills = relationship("ProfileSkill", back_populates="profile")
languages = relationship("ProfileLanguage", back_populates="profile")
def to_dict(self):
return {
"id": self.id,
"nickname": self.nickname
"nickname": self.nickname,
"pronouns": self.pronouns,
"volunteerwork": self.volunteerwork,
"freetext": self.freetext
}
@ -45,9 +63,11 @@ class Contact(db.Model):
__tablename__ = "contact"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="contacts")
contacttype_id = Column(Integer, ForeignKey("contacttype.id"), nullable=False)
profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="contacts")
contacttype_id = Column(Integer,
ForeignKey("contacttype.id"),
nullable=False)
contacttype = relationship("ContactType")
content = Column(String(200), nullable=False)
@ -71,8 +91,8 @@ class Address(db.Model):
city = Column(String(25), default="")
country = Column(String(25), default="")
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="address")
profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False)
profile = relationship("Profile", back_populates="address")
class Skill(db.Model):
@ -81,21 +101,21 @@ class Skill(db.Model):
id = Column(Integer, primary_key=True)
name = Column(String(25), unique=True, nullable=False)
users = relationship("UserSkill", back_populates="skill")
profiles = relationship("ProfileSkill", back_populates="skill")
def to_dict(self):
return {"id": self.id, "name": self.name}
class UserSkill(db.Model):
__tablename__ = "user_skill"
class ProfileSkill(db.Model):
__tablename__ = "profile_skill"
user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
profile_id = Column(Integer, ForeignKey("profile.id"), primary_key=True)
skill_id = Column(Integer, ForeignKey("skill.id"), primary_key=True)
level = Column(SmallInteger, nullable=False)
user = relationship("User", back_populates="skills")
skill = relationship("Skill", back_populates="users")
profile = relationship("Profile", back_populates="skills")
skill = relationship("Skill", back_populates="profiles")
class Language(db.Model):
@ -104,18 +124,18 @@ class Language(db.Model):
id = Column(String(2), primary_key=True)
name = Column(String(25), nullable=False)
users = relationship("UserLanguage", back_populates="language")
profiles = relationship("ProfileLanguage", back_populates="language")
def to_dict(self):
return {"id": self.id, "name": self.name}
class UserLanguage(db.Model):
__tablename__ = "user_language"
class ProfileLanguage(db.Model):
__tablename__ = "profile_language"
user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
profile_id = Column(Integer, ForeignKey("profile.id"), primary_key=True)
language_id = Column(Integer, ForeignKey("language.id"), primary_key=True)
level = Column(SmallInteger, nullable=False)
user = relationship("User", back_populates="languages")
language = relationship("Language", back_populates="users")
profile = relationship("Profile", back_populates="languages")
language = relationship("Language", back_populates="profiles")

View File

@ -3,8 +3,8 @@ from flask import g, make_response, request, send_file
from functools import wraps
from ki.auth import auth
from ki.models import Language, Skill, Token, User
from app import app
from ki.models import Language, Skill, Token, User, Profile
from app import app, db
def token_auth(func):
@ -110,7 +110,34 @@ def get_user_profile(user_id):
if user is None:
return make_response({}, 404)
return make_response({"user": user.to_dict()})
profile = user.profile
if profile is None:
return make_response({}, 404)
return make_response({"profile": profile.to_dict()})
@app.route("/users/<user_id>/profile", methods=["POST"])
def update_profile(user_id):
user = User.query.filter(User.id == int(user_id)).first()
if user is None:
return make_response({}, 404)
profile = user.profile
if (profile is None):
profile = Profile(user=user, nickname=user.auth_id)
db.session.add(profile)
profile.pronouns = request.json.get("pronouns", "")
profile.volunteerwork = request.json.get("volunteerwork", "")
profile.freetext = request.json.get("freetext", "")
db.session.commit()
return make_response(profile.to_dict(), 200)
@app.route("/skills")

View File

@ -0,0 +1,98 @@
from alembic import command
import unittest
import json
from app import app, db, migrate
from ki.commands import seed
from ki.models import Profile, User
class TestProfileEndpoint(unittest.TestCase):
def setUp(self):
app.debug = True
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
self.client = app.test_client()
with app.app_context():
config = migrate.get_config()
command.upgrade(config, "head")
seed()
def tearDown(self):
db.engine.dispose()
def test_create_profile(self):
user = User(auth_id="peter")
db.session.add(user)
db.session.commit()
login_data = {"username": "peter", "password": "geheim"}
login_response = self.client.post("/users/login",
data=json.dumps(login_data),
content_type="application/json")
self.assertEqual(login_response.status_code, 200)
self.assertIn("token", login_response.json)
data = {
"pronouns": "Herr Dr. Dr.",
"volunteerwork": "ja",
"freetext": "Hallo",
}
response = self.client.post("/users/1/profile",
data=json.dumps(data),
content_type="application/json",
headers={
"Authorization":
"Bearer " +
login_response.json["token"]
})
self.assertEqual(response.status_code, 200)
with app.app_context():
user = User.query.filter(User.id == 1).first()
profile = user.profile
self.assertEqual("Herr Dr. Dr.", profile.pronouns)
self.assertEqual("ja", profile.volunteerwork)
self.assertEqual("Hallo", profile.freetext)
def test_get_profile(self):
user = User(auth_id="peter")
db.session.add(user)
profile = Profile(user=user)
profile.nickname = "Popeter"
db.session.add(profile)
db.session.commit()
login_data = {"username": "peter", "password": "geheim"}
login_response = self.client.post("/users/login",
data=json.dumps(login_data),
content_type="application/json")
self.assertEqual(login_response.status_code, 200)
self.assertIn("token", login_response.json)
response = self.client.get("/users/1/profile",
headers={
"Authorization":
"Bearer " + login_response.json["token"]
})
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.json, {
"profile": {
"freetext": "",
"nickname": "Popeter",
"pronouns": "",
"volunteerwork": ""
}
})
if __name__ == "main":
unittest.main()

View File

@ -7,7 +7,7 @@
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
script_location = .
# Logging configuration
[loggers]

View File

@ -1,8 +1,8 @@
"""Initial migration
"""Initial migration.
Revision ID: 575a8924eb16
Revision ID: f95308aceda1
Revises:
Create Date: 2021-06-12 13:18:24.903142
Create Date: 2021-06-20 19:11:47.086814
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '575a8924eb16'
revision = 'f95308aceda1'
down_revision = None
branch_labels = None
depends_on = None
@ -28,13 +28,7 @@ def upgrade():
sa.Column('name', sa.String(length=25), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('skill',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=25), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user',
op.create_table('profile',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('nickname', sa.String(length=25), nullable=False),
sa.Column('pronouns', sa.String(length=25), nullable=True),
@ -42,11 +36,15 @@ def upgrade():
sa.Column('freetext', sa.String(length=4000), nullable=True),
sa.Column('created', sa.DateTime(), nullable=False),
sa.Column('updated', sa.DateTime(), nullable=False),
sa.Column('auth_id', sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('auth_id'),
sa.UniqueConstraint('nickname')
)
op.create_table('skill',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=25), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('address',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=25), nullable=True),
@ -56,54 +54,63 @@ def upgrade():
sa.Column('postcode', sa.String(length=10), nullable=True),
sa.Column('city', sa.String(length=25), nullable=True),
sa.Column('country', sa.String(length=25), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.Column('profile_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('contact',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('contacttype_id', sa.Integer(), nullable=True),
sa.Column('profile_id', sa.Integer(), nullable=False),
sa.Column('contacttype_id', sa.Integer(), nullable=False),
sa.Column('content', sa.String(length=200), nullable=False),
sa.ForeignKeyConstraint(['contacttype_id'], ['contacttype.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('token',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('token', sa.String(length=36), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user_language',
sa.Column('user_id', sa.Integer(), nullable=False),
op.create_table('profile_language',
sa.Column('profile_id', sa.Integer(), nullable=False),
sa.Column('language_id', sa.Integer(), nullable=False),
sa.Column('level', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['language_id'], ['language.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id', 'language_id')
sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ),
sa.PrimaryKeyConstraint('profile_id', 'language_id')
)
op.create_table('user_skill',
sa.Column('user_id', sa.Integer(), nullable=False),
op.create_table('profile_skill',
sa.Column('profile_id', sa.Integer(), nullable=False),
sa.Column('skill_id', sa.Integer(), nullable=False),
sa.Column('level', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ),
sa.ForeignKeyConstraint(['skill_id'], ['skill.id'], ),
sa.PrimaryKeyConstraint('profile_id', 'skill_id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('auth_id', sa.String(length=50), nullable=False),
sa.Column('profile_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('auth_id')
)
op.create_table('token',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('token', sa.String(length=36), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id', 'skill_id')
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user_skill')
op.drop_table('user_language')
op.drop_table('token')
op.drop_table('user')
op.drop_table('profile_skill')
op.drop_table('profile_language')
op.drop_table('contact')
op.drop_table('address')
op.drop_table('user')
op.drop_table('skill')
op.drop_table('profile')
op.drop_table('language')
op.drop_table('contacttype')
# ### end Alembic commands ###