0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-26 00:32:35 +01:00

ircd:Ⓜ️ Implement 14.18 Room Tagging.

This commit is contained in:
Jason Volk 2019-03-05 17:10:22 -08:00
parent 9e884b326d
commit 1f82b884ff
8 changed files with 777 additions and 9 deletions

View file

@ -30,6 +30,7 @@ struct ircd::m::user
struct profile;
struct account_data;
struct room_account_data;
struct room_tags;
struct filter;
using id = m::id::user;
@ -252,6 +253,44 @@ struct ircd::m::user::room_account_data
{}
};
struct ircd::m::user::room_tags
{
using closure_bool = std::function<bool (const string_view &key, const json::object &)>;
using closure = std::function<void (const string_view &key, const json::object &)>;
static constexpr const string_view &type_prefix
{
"ircd.room_tag"
};
static constexpr const size_t &typebuf_size
{
m::room::id::MAX_SIZE + size(type_prefix)
};
m::user user;
m::room room;
static string_view _type(const mutable_buffer &out, const m::room::id &);
static bool for_each(const m::user &, const m::room &, const closure_bool &);
static bool get(std::nothrow_t, const m::user &, const m::room &, const string_view &type, const closure &);
static event::id::buf set(const m::user &, const m::room &, const string_view &type, const json::object &value);
static bool del(const m::user &, const m::room &, const string_view &type);
public:
bool for_each(const closure_bool &) const;
bool get(std::nothrow_t, const string_view &type, const closure &) const;
void get(const string_view &type, const closure &) const;
json::object get(const mutable_buffer &out, const string_view &type) const; //nothrow
event::id::buf set(const string_view &type, const json::object &value) const;
bool del(const string_view &type) const;
room_tags(const m::user &user, const m::room &room)
:user{user}
,room{room}
{}
};
struct ircd::m::user::filter
{
using closure_bool = std::function<bool (const string_view &, const json::object &)>;

142
ircd/m.cc
View file

@ -3377,6 +3377,148 @@ ircd::m::user::room_account_data::_type(const mutable_buffer &out,
return function(out, room_id);
}
//
// user::room_tags
//
bool
ircd::m::user::room_tags::del(const string_view &type)
const
{
return del(user, room, type);
}
ircd::m::event::id::buf
ircd::m::user::room_tags::set(const string_view &type,
const json::object &val)
const
{
return set(user, room, type, val);
}
ircd::json::object
ircd::m::user::room_tags::get(const mutable_buffer &out,
const string_view &type)
const
{
json::object ret;
get(std::nothrow, type, [&out, &ret]
(const string_view &type, const json::object &val)
{
ret = string_view { data(out), copy(out, val) };
});
return ret;
}
void
ircd::m::user::room_tags::get(const string_view &type,
const closure &closure)
const
{
if(!get(std::nothrow, user, room, type, closure))
throw m::NOT_FOUND
{
"account data type '%s' for user %s in room %s not found",
type,
string_view{user.user_id},
string_view{room.room_id}
};
}
bool
ircd::m::user::room_tags::get(std::nothrow_t,
const string_view &type,
const closure &closure)
const
{
return get(std::nothrow, user, room, type, closure);
}
bool
ircd::m::user::room_tags::for_each(const closure_bool &closure)
const
{
return for_each(user, room, closure);
}
bool
ircd::m::user::room_tags::del(const m::user &u,
const m::room &r,
const string_view &t)
{
using prototype = bool (const m::user &, const m::room &, const string_view &);
static mods::import<prototype> function
{
"client_user", "ircd::m::user::room_tags::del"
};
return function(u, r, t);
}
ircd::m::event::id::buf
ircd::m::user::room_tags::set(const m::user &u,
const m::room &r,
const string_view &t,
const json::object &v)
{
using prototype = event::id::buf (const m::user &, const m::room &, const string_view &, const json::object &);
static mods::import<prototype> function
{
"client_user", "ircd::m::user::room_tags::set"
};
return function(u, r, t, v);
}
bool
ircd::m::user::room_tags::get(std::nothrow_t,
const m::user &u,
const m::room &r,
const string_view &t,
const closure &c)
{
using prototype = bool (std::nothrow_t, const m::user &, const m::room &, const string_view &, const closure &);
static mods::import<prototype> function
{
"client_user", "ircd::m::user::room_tags::get"
};
return function(std::nothrow, u, r, t, c);
}
bool
ircd::m::user::room_tags::for_each(const m::user &u,
const m::room &r,
const closure_bool &c)
{
using prototype = bool (const m::user &, const m::room &, const closure_bool &);
static mods::import<prototype> function
{
"client_user", "ircd::m::user::room_tags::for_each"
};
return function(u, r, c);
}
ircd::string_view
ircd::m::user::room_tags::_type(const mutable_buffer &out,
const m::room::id &room_id)
{
using prototype = string_view (const mutable_buffer &, const m::room::id &);
static mods::import<prototype> function
{
"client_user", "ircd::m::user::room_tags::_type"
};
return function(out, room_id);
}
//
// user::filter
//

View file

@ -131,6 +131,46 @@ get__initialsync_local(client &client,
return true;
});
const m::user::room_tags room_tags
{
user, room
};
json::stack::object tag
{
account_data
};
json::stack::member
{
tag, "type", "m.tag"
};
json::stack::object tag_content
{
tag, "content"
};
json::stack::object tags
{
tag_content, "tags"
};
room_tags.for_each([&tags]
(const string_view &type, const json::object &content)
{
json::stack::member
{
tags, type, content
};
return true;
});
tags.~object();
tag_content.~object();
tag.~object();
account_data.~array();
json::stack::array state

View file

@ -16,9 +16,13 @@ IRCD_MODULE
namespace ircd::m::sync
{
static bool room_account_data_polylog_tags(data &);
static bool room_account_data_polylog_events_event(data &, const m::event &);
static bool room_account_data_polylog_events(data &);
static bool room_account_data_polylog(data &);
static bool room_account_data_linear_tags(data &, const m::event &);
static bool room_account_data_linear_events(data &, const m::event &);
static bool room_account_data_linear(data &);
extern item room_account_data;
@ -42,37 +46,97 @@ ircd::m::sync::room_account_data_linear(data &data)
if(json::get<"room_id"_>(event) != data.user_room.room_id)
return false;
char typebuf[m::user::room_account_data::typebuf_size];
if(room_account_data_linear_events(data, event))
return true;
if(room_account_data_linear_tags(data, event))
return true;
return false;
}
bool
ircd::m::sync::room_account_data_linear_events(data &data,
const m::event &event)
{
if(!json::get<"state_key"_>(event))
return false;
const auto type
{
m::user::room_account_data::_type(typebuf, data.room->room_id)
split(json::get<"type"_>(event), '!')
};
if(json::get<"type"_>(event) != type)
if(type.first != "ircd.account_data")
return false;
const m::room room
{
lstrip(json::get<"type"_>(event), type.first)
};
json::stack::array array
{
*data.out, "events"
};
const scope_restore room_{data.room, &room};
return room_account_data_polylog_events_event(data, event);
}
bool
ircd::m::sync::room_account_data_linear_tags(data &data,
const m::event &event)
{
if(!json::get<"state_key"_>(event))
return false;
const auto type
{
split(json::get<"type"_>(event), '!')
};
if(type.first != "ircd.room_tag")
return false;
const m::room room
{
lstrip(json::get<"type"_>(event), type.first)
};
json::stack::array array
{
*data.out, "events"
};
// Due to room tags being "all one event" we have to iterate all of the
// tags for this room for this user polylog style. This is because the
// merge algorithm for linear /sync isn't sophisticated enough to see
// past the events[] array and know to combine all of room tags into
// the required format. The event_idx is hacked to 0 here to trick the
// polylog apropos() into composing all tags unconditionally.
const scope_restore range{data.range.first, 0UL};
const scope_restore room_{data.room, &room};
return room_account_data_polylog_tags(data);
}
bool
ircd::m::sync::room_account_data_polylog(data &data)
{
return room_account_data_polylog_events(data);
}
bool
ircd::m::sync::room_account_data_polylog_events(data &data)
{
json::stack::array array
{
*data.out, "events"
};
bool ret{false};
ret |= room_account_data_polylog_events(data);
ret |= room_account_data_polylog_tags(data);
return ret;
}
bool
ircd::m::sync::room_account_data_polylog_events(data &data)
{
assert(data.room);
char typebuf[m::user::room_account_data::typebuf_size];
const auto type
@ -127,3 +191,72 @@ ircd::m::sync::room_account_data_polylog_events_event(data &data,
return true;
}
bool
ircd::m::sync::room_account_data_polylog_tags(data &data)
{
json::stack::checkpoint checkpoint
{
*data.out
};
json::stack::object object
{
*data.out
};
json::stack::member
{
object, "type", "m.tag"
};
json::stack::object content
{
object, "content"
};
json::stack::object tags
{
content, "tags"
};
assert(data.room);
char typebuf[m::user::room_tags::typebuf_size];
const auto type
{
m::user::room_tags::_type(typebuf, data.room->room_id)
};
bool ret{false};
data.user_state.for_each(type, [&data, &tags, &ret]
(const m::event::idx &event_idx)
{
if(!apropos(data, event_idx))
return;
static const event::fetch::opts fopts
{
event::keys::include {"state_key", "content"}
};
const m::event::fetch event
{
event_idx, std::nothrow, fopts
};
if(!event.valid)
return;
json::stack::member tag
{
tags, json::get<"state_key"_>(event), json::get<"content"_>(event)
};
ret = true;
});
if(!ret)
checkpoint.rollback();
return ret;
}

View file

@ -12,6 +12,24 @@
using namespace ircd;
static resource::response
put__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room_id);
static resource::response
get__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room);
static resource::response
delete__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room);
static resource::response
put__account_data(client &client,
const resource::request &request,
@ -54,6 +72,9 @@ put__rooms(client &client,
if(cmd == "account_data")
return put__account_data(client, request, user_id, room_id);
if(cmd == "tags")
return put__tags(client, request, user_id, room_id);
throw m::NOT_FOUND
{
"/user/rooms/ command not found"
@ -90,12 +111,302 @@ get__rooms(client &client,
if(cmd == "account_data")
return get__account_data(client, request, user_id, room_id);
if(cmd == "tags")
return get__tags(client, request, user_id, room_id);
throw m::NOT_FOUND
{
"/user/rooms/ command not found"
};
}
resource::response
delete__rooms(client &client,
const resource::request &request,
const m::user::id &user_id)
{
if(request.parv.size() < 3)
throw m::NEED_MORE_PARAMS
{
"room_id required"
};
m::room::id::buf room_id
{
url::decode(room_id, request.parv[2])
};
if(request.parv.size() < 4)
throw m::NEED_MORE_PARAMS
{
"rooms command required"
};
const string_view &cmd
{
request.parv[3]
};
if(cmd == "tags")
return delete__tags(client, request, user_id, room_id);
throw m::NOT_FOUND
{
"/user/rooms/ command not found"
};
}
resource::response
put__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room)
{
if(request.parv.size() < 5)
throw m::NEED_MORE_PARAMS
{
"tag path parameter required"
};
char tagbuf[m::event::TYPE_MAX_SIZE];
const auto &tag
{
url::decode(tagbuf, request.parv[4])
};
const json::object &value
{
request
};
const m::user::room_tags room_tags
{
user, room
};
room_tags.set(tag, value);
return resource::response
{
client, http::OK
};
}
resource::response
get__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room)
{
const m::user::room_tags room_tags
{
user, room
};
resource::response::chunked response
{
client, http::OK
};
json::stack out
{
response.buf, response.flusher()
};
json::stack::object top{out};
json::stack::object tags
{
top, "tags"
};
room_tags.for_each([&tags]
(const string_view &type, const json::object &content)
{
json::stack::member
{
tags, type, content
};
return true;
});
return response;
}
resource::response
delete__tags(client &client,
const resource::request &request,
const m::user &user,
const m::room &room)
{
if(request.parv.size() < 5)
throw m::NEED_MORE_PARAMS
{
"tag path parameter required"
};
char tagbuf[m::event::TYPE_MAX_SIZE];
const auto &tag
{
url::decode(tagbuf, request.parv[4])
};
const m::user::room_tags room_tags
{
user, room
};
const bool deleted
{
room_tags.del(tag)
};
return resource::response
{
client, deleted? http::OK : http::NOT_FOUND
};
}
bool
IRCD_MODULE_EXPORT
ircd::m::user::room_tags::del(const m::user &user,
const m::room &room,
const string_view &user_type)
{
char typebuf[typebuf_size];
const string_view type
{
_type(typebuf, room.room_id)
};
const user::room user_room
{
user
};
const auto event_idx
{
user_room.get(std::nothrow, type, user_type)
};
if(!event_idx)
return false;
const auto event_id
{
m::event_id(event_idx)
};
redact(user_room, user, event_id, "deleted");
return true;
}
m::event::id::buf
IRCD_MODULE_EXPORT
ircd::m::user::room_tags::set(const m::user &user,
const m::room &room,
const string_view &user_type,
const json::object &value)
{
char typebuf[typebuf_size];
const string_view type
{
_type(typebuf, room.room_id)
};
const user::room user_room
{
user
};
return send(user_room, user, type, user_type, value);
}
bool
IRCD_MODULE_EXPORT
ircd::m::user::room_tags::get(std::nothrow_t,
const m::user &user,
const m::room &room,
const string_view &user_type,
const closure &closure)
{
char typebuf[typebuf_size];
const string_view type
{
_type(typebuf, room.room_id)
};
const user::room user_room
{
user
};
const event::idx event_idx
{
user_room.get(std::nothrow, type, user_type)
};
return event_idx && m::get(std::nothrow, event_idx, "content", [&user_type, &closure]
(const json::object &content)
{
closure(user_type, content);
});
}
bool
IRCD_MODULE_EXPORT
ircd::m::user::room_tags::for_each(const m::user &user,
const m::room &room,
const closure_bool &closure)
{
char typebuf[typebuf_size];
const string_view type
{
_type(typebuf, room.room_id)
};
static const event::fetch::opts fopts
{
event::keys::include {"state_key", "content"}
};
const user::room user_room{user};
const room::state state
{
user_room, &fopts
};
return state.for_each(type, event::closure_bool{[&closure]
(const m::event &event)
{
const auto &user_type
{
at<"state_key"_>(event)
};
const auto &content
{
json::get<"content"_>(event)
};
return closure(user_type, content);
}});
}
ircd::string_view
IRCD_MODULE_EXPORT
ircd::m::user::room_tags::_type(const mutable_buffer &out,
const m::room::id &room_id)
{
assert(size(out) >= typebuf_size);
string_view ret;
ret = strlcpy(out, type_prefix);
ret = strlcat(out, room_id);
return ret;
}
resource::response
put__account_data(client &client,
const resource::request &request,

View file

@ -174,3 +174,52 @@ put_method
put_method.REQUIRES_AUTH
}
};
resource::response
delete_user(client &client, const resource::request &request)
{
if(request.parv.size() < 1)
throw m::NEED_MORE_PARAMS
{
"user_id required"
};
m::user::id::buf user_id
{
url::decode(user_id, request.parv[0])
};
if(request.user_id != user_id)
throw m::UNSUPPORTED
{
"Deleting user data as someone else is not yet supported"
};
if(request.parv.size() < 2)
throw m::NEED_MORE_PARAMS
{
"user command required"
};
const string_view &cmd
{
request.parv[1]
};
if(cmd == "rooms")
return delete__rooms(client, request, user_id);
throw m::NOT_FOUND
{
"/user command not found"
};
}
resource::method
delete_method
{
user_resource, "DELETE", delete_user,
{
delete_method.REQUIRES_AUTH
}
};

View file

@ -64,3 +64,8 @@ ircd::resource::response
put__rooms(ircd::client &client,
const ircd::resource::request &request,
const ircd::m::user::id &user_id);
ircd::resource::response
delete__rooms(ircd::client &client,
const ircd::resource::request &request,
const ircd::m::user::id &user_id);

View file

@ -9200,6 +9200,55 @@ console_cmd__user__room_account_data(opt &out, const string_view &line)
return true;
}
bool
console_cmd__user__room_tags(opt &out, const string_view &line)
{
const params param{line, " ",
{
"user_id", "room_id", "tag"
}};
const m::user::id &user_id
{
param.at("user_id")
};
const auto &room_id
{
m::room_id(param.at("room_id"))
};
const string_view &tag
{
param["tag"]
};
const m::user::room_tags room_tags
{
user_id, room_id
};
if(tag)
{
room_tags.get(tag, [&out]
(const string_view &key, const json::object &val)
{
out << val << std::endl;
});
return true;
}
room_tags.for_each([&out]
(const string_view &key, const json::object &val)
{
out << key << ": " << val << std::endl;
return true;
});
return true;
}
bool
console_cmd__user__devices(opt &out, const string_view &line)
{