From 7fb5c47e4d559ea132c09bff01d7a2154d6a18c4 Mon Sep 17 00:00:00 2001 From: Mattia Succurro Date: Fri, 8 Nov 2019 16:51:32 +0100 Subject: [PATCH] Added new API endpoints --- CHANGELOG.md | 3 ++ app/api.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++--- app/models.py | 3 ++ app/website.py | 9 ++--- 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6759900..3828e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * Schema changes: moved `full_name` field from table `userprofile` to table `user` for search improvement reasons. * Adding `messages_count`, `followers_count` and `following_count` to `profile_info` API endpoint (what I've done to 0.7.1 too). +* Adding media URLs to messages in API. +* Added `relationships_follow`, `relationships_unfollow`, `username_availability` and `edit_profile` endpoints to API. +* Added `url` utility to model `Upload`. ## 0.7.1-dev diff --git a/app/api.py b/app/api.py index 05bbd94..95b7c42 100644 --- a/app/api.py +++ b/app/api.py @@ -1,13 +1,20 @@ from flask import Blueprint, jsonify, request import sys, datetime, re from functools import wraps -from .models import User, Message, Relationship, \ +from peewee import IntegrityError +from .models import User, Message, Relationship, database, \ MSGPRV_PUBLIC, MSGPRV_UNLISTED, MSGPRV_FRIENDS, MSGPRV_ONLYME -from .utils import check_access_token, Visibility +from .utils import check_access_token, Visibility, push_notification, unpush_notification bp = Blueprint('api', __name__, url_prefix='/api/V1') def get_message_info(message): + try: + media = message.uploads[0].url() + except IndexError: + media = None + if media: + print(media) return { 'id': message.id, 'user': { @@ -16,7 +23,8 @@ def get_message_info(message): }, 'text': message.text, 'privacy': message.privacy, - 'pub_date': message.pub_date.timestamp() + 'pub_date': message.pub_date.timestamp(), + 'media': media } def validate_access(func): @@ -163,6 +171,34 @@ def profile_feed(self, userid): timeline_media.append(get_message_info(message)) return {'timeline_media': timeline_media} +@bp.route('/relationships//follow', methods=['POST']) +@validate_access +def relationships_follow(self, userid): + user = User[userid] + try: + with database.atomic(): + Relationship.create( + from_user=self, + to_user=user, + created_date=datetime.datetime.now()) + except IntegrityError: + pass + push_notification('follow', user, user=self.id) + return get_relationship_info(self, user) + +@bp.route('/relationships//unfollow', methods=['POST']) +@validate_access +def relationships_unfollow(self, userid): + user = User[userid] + (Relationship + .delete() + .where( + (Relationship.from_user == self) & + (Relationship.to_user == user)) + .execute()) + unpush_notification('follow', user, user=self.id) + return get_relationship_info(self, user) + @bp.route('/profile_search', methods=['POST']) @validate_access def profile_search(self): @@ -180,4 +216,54 @@ def profile_search(self): return { "users": results } - + +@bp.route('/username_availability/') +@validate_access +def username_availability(self, username): + current = self.username + is_valid = is_username(username) + if is_valid: + try: + user = User.get(User.username == username) + is_available = current == user.username + except User.DoesNotExist: + is_available = True + else: + is_available = False + return { + 'is_valid': is_valid, + 'is_available': is_available + } + +@bp.route('/edit_profile', methods=['POST']) +@validate_access +def edit_profile(user): + data = request.get_json(True) + username = data['username'] + if not username: + # prevent username to be set to empty + username = user.username + if username != user.username: + try: + User.update(username=username).where(User.id == user.id).execute() + except IntegrityError: + raise ValueError('that username is already taken') + full_name = data['full_name'] or username + if full_name != user.full_name: + User.update(full_name=full_name).where(User.id == user.id).execute() + website = data['website'].strip().replace(' ', '%20') + if website and not validate_website(website): + raise ValueError('You should enter a valid URL.') + #location = int(request.form.get('location')) + #if location == 0: + # location = None + UserProfile.update( + biography=data['biography'], + #year=data['year'] if data.get('has_year') else None, + #location=location, + website=website, + instagram=data['instagram'], + facebook=data['facebook'], + telegram=data['telegram'] + ).where(UserProfile.user == user).execute() + return {} diff --git a/app/models.py b/app/models.py index 5c35108..e95a793 100644 --- a/app/models.py +++ b/app/models.py @@ -11,6 +11,7 @@ The tables are: * notification - a in-site notification to a user; new in 0.3 ''' +from flask import request from peewee import * import os # here should go `from .utils import get_current_user`, but it will cause @@ -188,6 +189,8 @@ class Upload(BaseModel): # helper to retrieve contents def filename(self): return str(self.id) + '.' + self.type + def url(self): + return request.host_url + 'uploads/' + self.filename() class Notification(BaseModel): type = TextField() diff --git a/app/website.py b/app/website.py index b5f8ee7..9d7d13d 100644 --- a/app/website.py +++ b/app/website.py @@ -60,13 +60,13 @@ def register(): # unique constraint, the database will raise an IntegrityError. user = User.create( username=username, + full_name=request.form.get('full_name') or username, password=pwdhash(request.form['password']), email=request.form['email'], birthday=birthday, join_date=datetime.datetime.now()) UserProfile.create( - user=user, - full_name=request.form.get('full_name') or username + user=user ) # mark the user as being 'authenticated' by setting the session vars @@ -260,7 +260,6 @@ def confirm_delete(id): def profile_checkpoint(): return UserProfile( user=get_current_user(), - full_name=request.form['full_name'], biography=request.form['biography'], location=int(request.form['location']), year=int(request.form['year'] if request.form.get('has_year') else '0'), @@ -285,6 +284,9 @@ def edit_profile(): except IntegrityError: flash('That username is already taken') return render_template('edit_profile.html', profile=profile_checkpoint()) + full_name = request.form['full_name'] or username + if full_name != user.full_name: + User.update(full_name=full_name).where(User.id == user.id).execute() website = request.form['website'].strip().replace(' ', '%20') if website and not validate_website(website): flash('You should enter a valid URL.') @@ -293,7 +295,6 @@ def edit_profile(): if location == 0: location = None UserProfile.update( - full_name=request.form['full_name'] or username, biography=request.form['biography'], year=request.form['year'] if request.form.get('has_year') else None, location=location,