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 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 \ curl -s \
-D "/dev/stderr" \ -D "/dev/stderr" \

View File

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

View File

@ -9,25 +9,43 @@ from app import db
class User(db.Model): class User(db.Model):
__tablename__ = "user" __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) id = Column(Integer, primary_key=True)
nickname = Column(String(25), unique=True, nullable=False) nickname = Column(String(25), unique=True, nullable=False)
pronouns = Column(String(25), default="") pronouns = Column(String(25), default="")
volunteerwork = Column(String(4000), default="") volunteerwork = Column(String(4000), default="")
freetext = Column(String(4000), default="") freetext = Column(String(4000), default="")
created = Column(DateTime, nullable=False, default=datetime.now) created = Column(DateTime, nullable=False, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now, nullable=False, default=datetime.now) updated = Column(DateTime,
auth_id = Column(String(50), nullable=False, unique=True) onupdate=datetime.now,
nullable=False,
default=datetime.now)
user = relationship("User", back_populates="profile", uselist=False)
contacts = relationship("Contact") contacts = relationship("Contact")
address = relationship("Address", uselist=False, back_populates="user") address = relationship("Address", uselist=False, back_populates="profile")
tokens = relationship("Token", uselist=False, back_populates="user") skills = relationship("ProfileSkill", back_populates="profile")
skills = relationship("UserSkill", back_populates="user") languages = relationship("ProfileLanguage", back_populates="profile")
languages = relationship("UserLanguage", back_populates="user")
def to_dict(self): def to_dict(self):
return { 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" __tablename__ = "contact"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("user.id"), nullable=False) profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False)
user = relationship("User", back_populates="contacts") profile = relationship("Profile", back_populates="contacts")
contacttype_id = Column(Integer, ForeignKey("contacttype.id"), nullable=False) contacttype_id = Column(Integer,
ForeignKey("contacttype.id"),
nullable=False)
contacttype = relationship("ContactType") contacttype = relationship("ContactType")
content = Column(String(200), nullable=False) content = Column(String(200), nullable=False)
@ -71,8 +91,8 @@ class Address(db.Model):
city = Column(String(25), default="") city = Column(String(25), default="")
country = Column(String(25), default="") country = Column(String(25), default="")
user_id = Column(Integer, ForeignKey("user.id"), nullable=False) profile_id = Column(Integer, ForeignKey("profile.id"), nullable=False)
user = relationship("User", back_populates="address") profile = relationship("Profile", back_populates="address")
class Skill(db.Model): class Skill(db.Model):
@ -81,21 +101,21 @@ class Skill(db.Model):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String(25), unique=True, nullable=False) name = Column(String(25), unique=True, nullable=False)
users = relationship("UserSkill", back_populates="skill") profiles = relationship("ProfileSkill", back_populates="skill")
def to_dict(self): def to_dict(self):
return {"id": self.id, "name": self.name} return {"id": self.id, "name": self.name}
class UserSkill(db.Model): class ProfileSkill(db.Model):
__tablename__ = "user_skill" __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) skill_id = Column(Integer, ForeignKey("skill.id"), primary_key=True)
level = Column(SmallInteger, nullable=False) level = Column(SmallInteger, nullable=False)
user = relationship("User", back_populates="skills") profile = relationship("Profile", back_populates="skills")
skill = relationship("Skill", back_populates="users") skill = relationship("Skill", back_populates="profiles")
class Language(db.Model): class Language(db.Model):
@ -104,18 +124,18 @@ class Language(db.Model):
id = Column(String(2), primary_key=True) id = Column(String(2), primary_key=True)
name = Column(String(25), nullable=False) name = Column(String(25), nullable=False)
users = relationship("UserLanguage", back_populates="language") profiles = relationship("ProfileLanguage", back_populates="language")
def to_dict(self): def to_dict(self):
return {"id": self.id, "name": self.name} return {"id": self.id, "name": self.name}
class UserLanguage(db.Model): class ProfileLanguage(db.Model):
__tablename__ = "user_language" __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) language_id = Column(Integer, ForeignKey("language.id"), primary_key=True)
level = Column(SmallInteger, nullable=False) level = Column(SmallInteger, nullable=False)
user = relationship("User", back_populates="languages") profile = relationship("Profile", back_populates="languages")
language = relationship("Language", back_populates="users") 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 functools import wraps
from ki.auth import auth from ki.auth import auth
from ki.models import Language, Skill, Token, User from ki.models import Language, Skill, Token, User, Profile
from app import app from app import app, db
def token_auth(func): def token_auth(func):
@ -110,7 +110,34 @@ def get_user_profile(user_id):
if user is None: if user is None:
return make_response({}, 404) 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") @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 # set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate # the 'revision' command, regardless of autogenerate
# revision_environment = false # revision_environment = false
script_location = .
# Logging configuration # Logging configuration
[loggers] [loggers]

View File

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