Initial implementation of extended profiles

This commit is contained in:
Erik Johnston 2016-10-26 14:58:13 +01:00
parent e438699c59
commit f83e0c1a27
7 changed files with 252 additions and 1 deletions

View file

@ -180,3 +180,17 @@ class ProfileHandler(BaseHandler):
"Failed to update join event for room %s - %s",
j.room_id, str(e.message)
)
def get_full_profile_for_user(self, user_id):
return self.store.get_full_profile(user_id)
def get_persona_profile_for_user(self, user_id, persona):
return self.store.get_persona_profile(user_id, persona)
def get_profile_key_for_user(self, user_id, persona, key):
return self.store.get_profile_key(user_id, persona, key)
def update_profile_key(self, user_id, persona, key, content):
return self.store.update_profile_key(
user_id, persona, key, content
)

View file

@ -50,6 +50,7 @@ from synapse.rest.client.v2_alpha import (
devices,
thirdparty,
sendtodevice,
profiles_extended,
)
from synapse.http.server import JsonResource
@ -98,3 +99,4 @@ class ClientRestResource(JsonResource):
devices.register_servlets(hs, client_resource)
thirdparty.register_servlets(hs, client_resource)
sendtodevice.register_servlets(hs, client_resource)
profiles_extended.register_servlets(hs, client_resource)

View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Copyright 2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import client_v2_patterns
from synapse.api.errors import NotFoundError
from synapse.http.servlet import RestServlet, parse_json_object_from_request
from twisted.internet import defer
import logging
logger = logging.getLogger(__name__)
class FullProfileServlet(RestServlet):
PATTERNS = client_v2_patterns(
"/profile_extended/(?P<user_id>[^/]+)/$"
)
EXPIRES_MS = 3600 * 1000
def __init__(self, hs):
super(FullProfileServlet, self).__init__()
self.auth = hs.get_auth()
self.profile_handler = hs.get_handlers().profile_handler
@defer.inlineCallbacks
def on_GET(self, request, user_id):
yield self.auth.get_user_by_req(request)
profile = yield self.profile_handler.get_full_profile_for_user(user_id)
defer.returnValue((200, profile))
class ProfilePersonaServlet(RestServlet):
PATTERNS = client_v2_patterns(
"/profile_extended/(?P<user_id>[^/]+)/(?P<persona>[^/]+)/$"
)
EXPIRES_MS = 3600 * 1000
def __init__(self, hs):
super(ProfilePersonaServlet, self).__init__()
self.auth = hs.get_auth()
self.profile_handler = hs.get_handlers().profile_handler
@defer.inlineCallbacks
def on_GET(self, request, user_id, persona):
yield self.auth.get_user_by_req(request)
profile = yield self.profile_handler.get_persona_profile_for_user(
user_id, persona
)
if profile:
defer.returnValue((200, profile))
else:
raise NotFoundError()
class ProfileTupleServlet(RestServlet):
PATTERNS = client_v2_patterns(
"/profile_extended/(?P<user_id>[^/]+)/(?P<persona>[^/]+)/(?P<key>[^/]+)$"
)
EXPIRES_MS = 3600 * 1000
def __init__(self, hs):
super(ProfileTupleServlet, self).__init__()
self.auth = hs.get_auth()
self.profile_handler = hs.get_handlers().profile_handler
@defer.inlineCallbacks
def on_GET(self, request, user_id, persona, key):
yield self.auth.get_user_by_req(request)
profile = yield self.profile_handler.get_profile_key_for_user(
user_id, persona, key
)
if profile is not None:
defer.returnValue((200, profile))
else:
raise NotFoundError()
@defer.inlineCallbacks
def on_PUT(self, request, user_id, persona, key):
yield self.auth.get_user_by_req(request)
content = parse_json_object_from_request(request)
yield self.profile_handler.update_profile_key(user_id, persona, key, content)
defer.returnValue((200, {}))
def register_servlets(hs, http_server):
FullProfileServlet(hs).register(http_server)
ProfileTupleServlet(hs).register(http_server)
ProfilePersonaServlet(hs).register(http_server)

View file

@ -116,6 +116,9 @@ class DataStore(RoomMemberStore, RoomStore,
self._public_room_id_gen = StreamIdGenerator(
db_conn, "public_room_list_stream", "stream_id"
)
self._profiles_id_gen = StreamIdGenerator(
db_conn, "profiles_extended", "stream_id"
)
self._transaction_id_gen = IdGenerator(db_conn, "sent_transactions", "id")
self._state_groups_id_gen = IdGenerator(db_conn, "state_groups", "id")

View file

@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
# Remember to update this number every time a change is made to database
# schema files, so the users will be informed on server restarts.
SCHEMA_VERSION = 37
SCHEMA_VERSION = 38
dir_path = os.path.abspath(os.path.dirname(__file__))

View file

@ -13,8 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from twisted.internet import defer
from ._base import SQLBaseStore
import ujson
class ProfileStore(SQLBaseStore):
def create_profile(self, user_localpart):
@ -55,3 +59,91 @@ class ProfileStore(SQLBaseStore):
updatevalues={"avatar_url": new_avatar_url},
desc="set_profile_avatar_url",
)
@defer.inlineCallbacks
def get_full_profile(self, user_id):
rows = yield self._simple_select_list(
table="profiles_extended",
keyvalues={"user_id": user_id},
retcols=("persona", "key", "content",),
)
personas = {}
profile = {"personas": personas}
for row in rows:
content = ujson.loads(row["content"])
personas.setdefault(
row["persona"], {"rows": {}}
)["entries"][row["key"]] = content
defer.returnValue(profile)
@defer.inlineCallbacks
def get_persona_profile(self, user_id, persona):
rows = yield self._simple_select_list(
table="profiles_extended",
keyvalues={
"user_id": user_id,
"persona": persona,
},
retcols=("key", "content",),
)
persona = {"rows": {
row["key"]: ujson.loads(row["content"])
for row in rows
}}
defer.returnValue(persona)
@defer.inlineCallbacks
def get_profile_key(self, user_id, persona, key):
content_json = yield self._simple_select_one_onecol(
table="profiles_extended",
keyvalues={
"user_id": user_id,
"persona": persona,
"key": key,
},
retcol="content",
allow_none=True,
)
if content_json:
content = ujson.loads(content_json)
else:
content = None
defer.returnValue(content)
def update_profile_key(self, user_id, persona, key, content):
content_json = ujson.dumps(content)
def _update_profile_key_txn(txn, stream_id):
self._simple_delete_txn(
txn,
table="profiles_extended",
keyvalues={
"user_id": user_id,
"persona": persona,
"key": key,
}
)
self._simple_insert_txn(
txn,
table="profiles_extended",
values={
"stream_id": stream_id,
"user_id": user_id,
"persona": persona,
"key": key,
"content": content_json,
}
)
with self._profiles_id_gen.get_next() as stream_id:
return self.runInteraction(
"update_profile_key", _update_profile_key_txn,
stream_id,
)

View file

@ -0,0 +1,26 @@
/* Copyright 2016 OpenMarket Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
CREATE TABLE profiles_extended (
stream_id BIGINT NOT NULL,
user_id TEXT NOT NULL,
persona TEXT NOT NULL,
key TEXT NOT NULL,
content TEXT NOT NULL
);
CREATE INDEX profiles_extended_tuple ON profiles_extended(user_id, persona, key, stream_id);