mirror of
https://github.com/matrix-construct/construct
synced 2024-09-28 11:48:54 +02:00
Checkpoint matrix application basis.
This commit is contained in:
parent
196d158398
commit
c032c686f6
21 changed files with 1835 additions and 617 deletions
|
@ -248,15 +248,104 @@ try
|
|||
break;
|
||||
}
|
||||
|
||||
const auto args(tokens_after(line, " ", 0));
|
||||
const params token{args, " ", {"room_id"}};
|
||||
const auto room_id{token.at(0, "!ircd:cdc.z"s)};
|
||||
|
||||
m::request request
|
||||
{
|
||||
"GET", "_matrix/client/r0/events"
|
||||
"GET", "_matrix/client/r0/events", {}, json::members
|
||||
{
|
||||
{ "room_id", string_view{room_id} }
|
||||
}
|
||||
};
|
||||
|
||||
static char buf[1024];
|
||||
static char buf[65536];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const auto doc((*moi)(pb, request));
|
||||
std::cout << doc << std::endl;
|
||||
const json::object response{(*moi)(pb, request)};
|
||||
const json::array chunk(response["chunk"]);
|
||||
for(const string_view &chunk : chunk)
|
||||
std::cout << chunk << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case hash("context"):
|
||||
{
|
||||
if(!moi)
|
||||
{
|
||||
std::cerr << "No current session" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto args(tokens_after(line, " ", 0));
|
||||
const params token{args, " ", {"room_id", "event_id"}};
|
||||
const auto &room_id{token.at(0)};
|
||||
const auto &event_id{token.at(1)};
|
||||
|
||||
char url[512]; const auto url_len
|
||||
{
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/context/%s",
|
||||
room_id,
|
||||
event_id)
|
||||
};
|
||||
|
||||
m::request request
|
||||
{
|
||||
"GET", string_view{url, size_t(url_len)}, {}
|
||||
};
|
||||
|
||||
static char buf[65536];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const json::object response{(*moi)(pb, request)};
|
||||
for(const auto &member : response)
|
||||
std::cout << member.first << " " << member.second << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case hash("state"):
|
||||
{
|
||||
if(!moi)
|
||||
{
|
||||
std::cerr << "No current session" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto args(tokens_after(line, " ", 0));
|
||||
const params token{args, " ", {"room_id", "event_type", "state_key"}};
|
||||
const auto &room_id{token.at(0)};
|
||||
const auto event_type{token[1]};
|
||||
const auto state_key{token[2]};
|
||||
|
||||
char url[512]; const auto url_len
|
||||
{
|
||||
event_type && state_key?
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state/%s/%s",
|
||||
room_id,
|
||||
event_type,
|
||||
state_key):
|
||||
|
||||
event_type?
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state/%s",
|
||||
room_id,
|
||||
event_type):
|
||||
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/rooms/%s/state",
|
||||
room_id)
|
||||
};
|
||||
|
||||
m::request request
|
||||
{
|
||||
"GET", string_view{url, size_t(url_len)}, {}
|
||||
};
|
||||
|
||||
static char buf[65536];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const json::array response{(*moi)(pb, request)};
|
||||
for(const auto &event : response)
|
||||
std::cout << event << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -336,7 +425,7 @@ try
|
|||
|
||||
static char buf[4096];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const auto doc((*moi)(pb, request));
|
||||
const string_view doc((*moi)(pb, request));
|
||||
std::cout << doc << std::endl;
|
||||
break;
|
||||
}
|
||||
|
@ -350,7 +439,32 @@ try
|
|||
}
|
||||
|
||||
const auto args(tokens_after(line, " ", 0));
|
||||
const params token{args, " ", {"username", "password"}};
|
||||
if(args.empty())
|
||||
{
|
||||
m::request request
|
||||
{
|
||||
"GET", "_matrix/client/r0/login", {}, {}
|
||||
};
|
||||
|
||||
static char buf[4096];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const json::object doc((*moi)(pb, request));
|
||||
const json::array flows(doc.at("flows"));
|
||||
|
||||
size_t i(0);
|
||||
for(const auto &flow : flows)
|
||||
std::cout << i++ << ": " << flow << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const params token
|
||||
{
|
||||
args, " ",
|
||||
{
|
||||
"username", "password"
|
||||
}
|
||||
};
|
||||
|
||||
m::request request
|
||||
{
|
||||
|
@ -364,7 +478,7 @@ try
|
|||
|
||||
static char buf[4096];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const auto doc((*moi)(pb, request));
|
||||
const json::object doc((*moi)(pb, request));
|
||||
std::cout << doc << std::endl;
|
||||
moi->access_token = std::string(unquote(doc.at("access_token")));
|
||||
break;
|
||||
|
@ -475,6 +589,102 @@ try
|
|||
break;
|
||||
}
|
||||
*/
|
||||
case hash("setfilter"):
|
||||
{
|
||||
if(!moi)
|
||||
{
|
||||
std::cerr << "No current session" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto args
|
||||
{
|
||||
tokens_after(line, " ", 0)
|
||||
};
|
||||
|
||||
const auto user_id
|
||||
{
|
||||
token(args, " ", 0)
|
||||
};
|
||||
|
||||
const json::object filter
|
||||
{
|
||||
tokens_after(args, " ", 0)
|
||||
};
|
||||
|
||||
static char url[128]; const auto url_len
|
||||
{
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/user/%s/filter", user_id)
|
||||
};
|
||||
|
||||
static char query[512]; const auto query_len
|
||||
{
|
||||
fmt::snprintf(query, sizeof(query), "%s=%s",
|
||||
"access_token",
|
||||
moi->access_token)
|
||||
};
|
||||
|
||||
m::request request
|
||||
{
|
||||
"POST", url, query, filter
|
||||
};
|
||||
|
||||
static char buf[4096];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const json::object response{(*moi)(pb, request)};
|
||||
std::cout << string_view{response} << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
case hash("getfilter"):
|
||||
{
|
||||
if(!moi)
|
||||
{
|
||||
std::cerr << "No current session" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto args
|
||||
{
|
||||
tokens_after(line, " ", 0)
|
||||
};
|
||||
|
||||
const auto user_id
|
||||
{
|
||||
token(args, " ", 0)
|
||||
};
|
||||
|
||||
const auto filter_id
|
||||
{
|
||||
tokens_after(args, " ", 0)
|
||||
};
|
||||
|
||||
static char url[128]; const auto url_len
|
||||
{
|
||||
fmt::snprintf(url, sizeof(url), "_matrix/client/r0/user/%s/filter/%s",
|
||||
user_id,
|
||||
filter_id)
|
||||
};
|
||||
|
||||
static char query[512]; const auto query_len
|
||||
{
|
||||
fmt::snprintf(query, sizeof(query), "%s=%s",
|
||||
"access_token",
|
||||
moi->access_token)
|
||||
};
|
||||
|
||||
m::request request
|
||||
{
|
||||
"GET", url, query, {}
|
||||
};
|
||||
|
||||
static char buf[4096];
|
||||
ircd::parse::buffer pb{buf};
|
||||
const json::object response{(*moi)(pb, request)};
|
||||
std::cout << string_view{response} << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
std::cerr << "Bad command or filename" << std::endl;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@
|
|||
#include "m/error.h"
|
||||
#include "m/id.h"
|
||||
#include "m/event.h"
|
||||
#include "m/events.h"
|
||||
#include "m/room.h"
|
||||
#include "m/timeline.h"
|
||||
#include "m/user.h"
|
||||
#include "m/filter.h"
|
||||
#include "m/request.h"
|
||||
#include "m/session.h"
|
||||
|
||||
|
|
|
@ -50,33 +50,47 @@ namespace ircd::m
|
|||
|
||||
namespace ircd::m::name
|
||||
{
|
||||
extern constexpr const char *const content {"content"};
|
||||
extern constexpr const char *const event_id {"event_id"};
|
||||
extern constexpr const char *const origin_server_ts {"origin_server_ts"};
|
||||
extern constexpr const char *const prev_ids {"prev_ids"};
|
||||
extern constexpr const char *const room_id {"room_id"};
|
||||
extern constexpr const char *const sender {"sender"};
|
||||
extern constexpr const char *const signatures {"signatures"};
|
||||
extern constexpr const char *const state_key {"state_key"};
|
||||
extern constexpr const char *const type {"type"};
|
||||
extern constexpr const char *const unsigned_ {"unsigned"};
|
||||
constexpr const char *const content {"content"};
|
||||
constexpr const char *const event_id {"event_id"};
|
||||
constexpr const char *const origin_server_ts {"origin_server_ts"};
|
||||
constexpr const char *const prev_ids {"prev_ids"};
|
||||
constexpr const char *const room_id {"room_id"};
|
||||
constexpr const char *const sender {"sender"};
|
||||
constexpr const char *const signatures {"signatures"};
|
||||
constexpr const char *const state_key {"state_key"};
|
||||
constexpr const char *const type {"type"};
|
||||
constexpr const char *const unsigned_ {"unsigned"};
|
||||
}
|
||||
|
||||
struct ircd::m::event
|
||||
:json::tuple
|
||||
<
|
||||
json::property<name::event_id, string_view>,
|
||||
json::property<name::content, string_view>,
|
||||
json::property<name::event_id, string_view>,
|
||||
json::property<name::origin_server_ts, time_t>,
|
||||
json::property<name::sender, string_view>,
|
||||
json::property<name::type, string_view>,
|
||||
json::property<name::room_id, string_view>,
|
||||
json::property<name::state_key, string_view>,
|
||||
json::property<name::prev_ids, string_view>,
|
||||
json::property<name::unsigned_, string_view>,
|
||||
json::property<name::signatures, string_view>
|
||||
json::property<name::room_id, string_view>,
|
||||
json::property<name::sender, string_view>,
|
||||
json::property<name::signatures, string_view>,
|
||||
json::property<name::state_key, string_view>,
|
||||
json::property<name::type, string_view>,
|
||||
json::property<name::unsigned_, string_view>
|
||||
>
|
||||
{
|
||||
using id = m::id::event;
|
||||
|
||||
static database *events;
|
||||
using cursor = db::cursor<events, event>;
|
||||
using const_iterator = cursor::const_iterator;
|
||||
using iterator = const_iterator;
|
||||
using where = cursor::where_type;
|
||||
|
||||
// Queue of contexts waiting to see the next inserted event
|
||||
static ctx::view<const event> inserted;
|
||||
|
||||
static const_iterator find(const id &);
|
||||
static void insert(json::iov &);
|
||||
|
||||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* charybdis: 21st Century IRC++d
|
||||
*
|
||||
* Copyright (C) 2016 Charybdis Development Team
|
||||
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice is present in all copies.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_EVENTS_H
|
||||
|
||||
namespace ircd::m::events
|
||||
{
|
||||
IRCD_M_EXCEPTION(m::error, INVALID_TRANSITION, http::BAD_REQUEST)
|
||||
|
||||
struct transition;
|
||||
|
||||
using transition_list = std::list<struct transition *>;
|
||||
extern transition_list transitions;
|
||||
|
||||
extern database *events;
|
||||
using cursor = db::cursor<events, m::event>;
|
||||
using const_iterator = cursor::const_iterator;
|
||||
using iterator = const_iterator;
|
||||
using where = cursor::where_type;
|
||||
|
||||
const_iterator find(const id &);
|
||||
const_iterator begin();
|
||||
const_iterator end();
|
||||
|
||||
void insert(const event &);
|
||||
void insert(json::iov &);
|
||||
}
|
||||
|
||||
struct ircd::m::events::transition
|
||||
{
|
||||
struct method
|
||||
{
|
||||
std::function<bool (const event &)> valid;
|
||||
std::function<void (const event &) noexcept> effects;
|
||||
};
|
||||
|
||||
const char *name;
|
||||
struct method method;
|
||||
unique_const_iterator<events::transition_list> it;
|
||||
|
||||
virtual bool valid(const event &event) const;
|
||||
virtual void effects(const event &e);
|
||||
|
||||
transition(const char *const &name, struct method method);
|
||||
transition(const char *const &name);
|
||||
virtual ~transition() noexcept;
|
||||
};
|
114
include/ircd/m/filter.h
Normal file
114
include/ircd/m/filter.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* charybdis: 21st Century IRC++d
|
||||
*
|
||||
* Copyright (C) 2016 Charybdis Development Team
|
||||
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice is present in all copies.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_FILTER_H
|
||||
|
||||
namespace ircd::m
|
||||
{
|
||||
struct filter;
|
||||
struct room_filter;
|
||||
struct event_filter;
|
||||
struct room_event_filter;
|
||||
}
|
||||
|
||||
namespace ircd::m::name
|
||||
{
|
||||
constexpr const char *const event_fields {"event_fields"};
|
||||
constexpr const char *const event_format {"event_format"};
|
||||
constexpr const char *const account_data {"account_data"};
|
||||
constexpr const char *const presence {"presence"};
|
||||
constexpr const char *const room {"room"};
|
||||
constexpr const char *const timeline {"timeline"};
|
||||
constexpr const char *const ephemeral {"ephemeral"};
|
||||
constexpr const char *const state {"state"};
|
||||
constexpr const char *const rooms {"rooms"};
|
||||
constexpr const char *const not_rooms {"not_rooms"};
|
||||
constexpr const char *const include_leave {"include_leave"};
|
||||
constexpr const char *const types {"types"};
|
||||
constexpr const char *const not_types {"not_types"};
|
||||
constexpr const char *const senders {"senders"};
|
||||
constexpr const char *const not_senders {"not_senders"};
|
||||
constexpr const char *const limit {"limit"};
|
||||
}
|
||||
|
||||
struct ircd::m::event_filter
|
||||
:json::tuple
|
||||
<
|
||||
json::property<name::limit, uint>,
|
||||
json::property<name::types, json::array>,
|
||||
json::property<name::senders, json::array>,
|
||||
json::property<name::not_types, json::array>,
|
||||
json::property<name::not_senders, json::array>
|
||||
>
|
||||
{
|
||||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
||||
|
||||
struct ircd::m::room_event_filter
|
||||
:json::tuple
|
||||
<
|
||||
json::property<name::limit, uint>,
|
||||
json::property<name::types, json::array>,
|
||||
json::property<name::rooms, json::array>,
|
||||
json::property<name::senders, json::array>,
|
||||
json::property<name::not_types, json::array>,
|
||||
json::property<name::not_rooms, json::array>,
|
||||
json::property<name::not_senders, json::array>
|
||||
>
|
||||
{
|
||||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
||||
|
||||
struct ircd::m::room_filter
|
||||
:json::tuple
|
||||
<
|
||||
json::property<name::rooms, json::array>,
|
||||
json::property<name::not_rooms, json::array>,
|
||||
json::property<name::state, room_event_filter>,
|
||||
json::property<name::timeline, room_event_filter>,
|
||||
json::property<name::ephemeral, room_event_filter>,
|
||||
json::property<name::account_data, room_event_filter>,
|
||||
json::property<name::include_leave, bool>
|
||||
>
|
||||
{
|
||||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
||||
|
||||
struct ircd::m::filter
|
||||
:json::tuple
|
||||
<
|
||||
json::property<name::event_fields, json::array>,
|
||||
json::property<name::event_format, string_view>,
|
||||
json::property<name::account_data, event_filter>,
|
||||
json::property<name::room, room_filter>,
|
||||
json::property<name::presence, event_filter>
|
||||
>
|
||||
{
|
||||
using super_type::tuple;
|
||||
using super_type::operator=;
|
||||
};
|
|
@ -100,7 +100,10 @@ template<class T,
|
|||
struct ircd::m::id::buf
|
||||
:T
|
||||
{
|
||||
static constexpr const size_t &SIZE{MAX};
|
||||
static constexpr const size_t SIZE
|
||||
{
|
||||
MAX
|
||||
};
|
||||
|
||||
private:
|
||||
std::array<char, SIZE> b;
|
||||
|
|
|
@ -34,42 +34,85 @@ namespace ircd::m
|
|||
struct room;
|
||||
}
|
||||
|
||||
namespace ircd::m::rooms
|
||||
{
|
||||
}
|
||||
|
||||
struct ircd::m::room
|
||||
{
|
||||
struct alias;
|
||||
struct events;
|
||||
struct state;
|
||||
|
||||
string_view room_id;
|
||||
using id = m::id::room;
|
||||
|
||||
using event_closure = std::function<void (const event &)>;
|
||||
void for_each(const events::where &, const event_closure &) const;
|
||||
void for_each(const event_closure &) const;
|
||||
id room_id;
|
||||
|
||||
using event_closure_bool = std::function<bool (const event &)>;
|
||||
size_t count(const events::where &, const event_closure_bool &) const;
|
||||
size_t count(const events::where &) const;
|
||||
bool any(const events::where &, const event_closure_bool &) const;
|
||||
bool any(const events::where &) const;
|
||||
|
||||
bool is_member(const m::id::user &, const string_view &membership = "join");
|
||||
void send(json::iov &event);
|
||||
void send(const json::members &event);
|
||||
|
||||
bool is_member(const m::id::user &, const string_view &membership = "join");
|
||||
void membership(const m::id::user &, json::iov &content);
|
||||
void leave(const m::id::user &, json::iov &content);
|
||||
void join(const m::id::user &, json::iov &content);
|
||||
|
||||
room(const id::room &room_id);
|
||||
room(const id::alias &alias);
|
||||
room(const id &room_id)
|
||||
:room_id{room_id}
|
||||
{}
|
||||
|
||||
room(const id::alias &alias)
|
||||
:room_id{}
|
||||
{}
|
||||
};
|
||||
|
||||
inline
|
||||
ircd::m::room::room(const id::room &room_id)
|
||||
:room_id{room_id}
|
||||
{}
|
||||
struct ircd::m::room::events
|
||||
{
|
||||
id room_id;
|
||||
|
||||
inline
|
||||
ircd::m::room::room(const id::alias &alias)
|
||||
:room_id{}
|
||||
{}
|
||||
using event_closure = std::function<void (const event &)>;
|
||||
using event_closure_bool = std::function<bool (const event &)>;
|
||||
|
||||
bool query(const event::where &, const event_closure_bool &) const;
|
||||
bool rquery(const event::where &, const event_closure_bool &) const;
|
||||
void for_each(const event::where &, const event_closure &) const;
|
||||
void rfor_each(const event::where &, const event_closure &) const;
|
||||
size_t count(const event::where &, const event_closure_bool &) const;
|
||||
bool any(const event::where &, const event_closure_bool &) const;
|
||||
|
||||
bool query(const event_closure_bool &) const;
|
||||
bool rquery(const event_closure_bool &) const;
|
||||
void for_each(const event_closure &) const;
|
||||
void rfor_each(const event_closure &) const;
|
||||
size_t count(const event::where &) const;
|
||||
bool any(const event::where &) const;
|
||||
|
||||
events(const id &room_id)
|
||||
:room_id{room_id}
|
||||
{}
|
||||
|
||||
events(const room &room)
|
||||
:room_id{room.room_id}
|
||||
{}
|
||||
};
|
||||
|
||||
struct ircd::m::room::state
|
||||
{
|
||||
id room_id;
|
||||
|
||||
using event_closure = std::function<void (const event &)>;
|
||||
using event_closure_bool = std::function<bool (const event &)>;
|
||||
|
||||
bool query(const event::where &, const event_closure_bool &) const;
|
||||
void for_each(const event::where &, const event_closure &) const;
|
||||
size_t count(const event::where &, const event_closure_bool &) const;
|
||||
bool any(const event::where &, const event_closure_bool &) const;
|
||||
|
||||
bool query(const event_closure_bool &) const;
|
||||
void for_each(const event_closure &) const;
|
||||
size_t count(const event::where &) const;
|
||||
bool any(const event::where &) const;
|
||||
|
||||
state(const id &room_id)
|
||||
:room_id{room_id}
|
||||
{}
|
||||
|
||||
state(const room &room)
|
||||
:room_id{room.room_id}
|
||||
{}
|
||||
};
|
||||
|
|
|
@ -23,23 +23,27 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_TIMELINE_H
|
||||
#define HAVE_IRCD_M_USER_H
|
||||
|
||||
namespace ircd {
|
||||
namespace m {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The timeline class represents a sequence of events. This is the preferred
|
||||
// interface to the events DB. The timeline can be used to compose a selection
|
||||
// of events and run algorithms over them. The timeline interface can also be
|
||||
// used to insert new events. Timelines form the basis for composing higher
|
||||
// level structures like rooms.
|
||||
//
|
||||
struct timeline
|
||||
namespace ircd::m
|
||||
{
|
||||
struct user;
|
||||
}
|
||||
|
||||
namespace ircd::m::users
|
||||
{
|
||||
}
|
||||
|
||||
struct ircd::m::user
|
||||
{
|
||||
using id = m::id::user;
|
||||
|
||||
id user_id;
|
||||
|
||||
user(const id &user_id);
|
||||
};
|
||||
|
||||
} // namespace m
|
||||
} // namespace ircd
|
||||
inline
|
||||
ircd::m::user::user(const id &user_id)
|
||||
:user_id{user_id}
|
||||
{}
|
972
ircd/matrix.cc
972
ircd/matrix.cc
File diff suppressed because it is too large
Load diff
|
@ -131,7 +131,7 @@ try
|
|||
{ "state_key", access_token }
|
||||
};
|
||||
|
||||
if(!accounts.any(query))
|
||||
if(!m::room::events{accounts}.any(query))
|
||||
throw m::error
|
||||
{
|
||||
// When credentials are required but missing or invalid, the HTTP call will return with
|
||||
|
@ -215,6 +215,13 @@ catch(const json::error &e)
|
|||
http::BAD_REQUEST, "M_NOT_JSON", "%s", e.what()
|
||||
};
|
||||
}
|
||||
catch(const std::out_of_range &e)
|
||||
{
|
||||
throw m::error
|
||||
{
|
||||
http::NOT_FOUND, "M_NOT_FOUND", "%s", e.what()
|
||||
};
|
||||
}
|
||||
|
||||
ircd::resource::method::method(struct resource &resource,
|
||||
const string_view &name,
|
||||
|
|
|
@ -39,33 +39,44 @@ const m::id::room::buf locops_room_id
|
|||
|
||||
const m::id::room::buf ircd_room_id
|
||||
{
|
||||
"ircd", "localhost"
|
||||
"ircd", "cdc.z"
|
||||
};
|
||||
|
||||
resource::response
|
||||
get_events(client &client, const resource::request &request)
|
||||
{
|
||||
m::room room{accounts_room_id};
|
||||
std::vector<std::string> ret;
|
||||
|
||||
room.for_each([&ret](const auto &event)
|
||||
m::room room
|
||||
{
|
||||
ret.emplace_back(json::string(event));
|
||||
m::id::room
|
||||
{
|
||||
unquote(request.at("room_id"))
|
||||
}
|
||||
};
|
||||
|
||||
const m::room::events events
|
||||
{
|
||||
room
|
||||
};
|
||||
|
||||
size_t i(0);
|
||||
events.for_each([&i](const auto &event)
|
||||
{
|
||||
++i;
|
||||
});
|
||||
|
||||
std::vector<json::object> jo(ret.size());
|
||||
std::transform(ret.begin(), ret.end(), jo.begin(), []
|
||||
(const auto &string) -> json::object
|
||||
size_t j(0);
|
||||
json::value ret[i];
|
||||
events.for_each([&i, &j, &ret](const m::event &event)
|
||||
{
|
||||
return string;
|
||||
if(j < i)
|
||||
ret[j++] = event;
|
||||
});
|
||||
|
||||
char buf[16384];
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
client, json::members
|
||||
{
|
||||
{ "chunk", json::stringify(buf, jo) }
|
||||
{ "chunk", { ret, j } }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,14 +34,14 @@ const auto home_server
|
|||
"cdc.z"
|
||||
};
|
||||
|
||||
namespace name
|
||||
namespace { namespace name
|
||||
{
|
||||
extern constexpr auto password{"password"};
|
||||
extern constexpr auto medium{"medium"};
|
||||
extern constexpr auto type{"type"};
|
||||
extern constexpr auto user{"user"};
|
||||
extern constexpr auto address{"address"};
|
||||
}
|
||||
constexpr const auto password{"password"};
|
||||
constexpr const auto medium{"medium"};
|
||||
constexpr const auto type{"type"};
|
||||
constexpr const auto user{"user"};
|
||||
constexpr const auto address{"address"};
|
||||
}}
|
||||
|
||||
struct body
|
||||
:json::tuple
|
||||
|
@ -69,15 +69,19 @@ m::room accounts_room
|
|||
accounts_room_id
|
||||
};
|
||||
|
||||
const m::room::events accounts_room_events
|
||||
{
|
||||
accounts_room
|
||||
};
|
||||
|
||||
resource::response
|
||||
post_login_password(client &client,
|
||||
const resource::request &request,
|
||||
const resource::request::body<body> &body)
|
||||
const resource::request::object<body> &request)
|
||||
{
|
||||
// Build a canonical MXID from a the user field
|
||||
const m::id::user::buf user_id
|
||||
{
|
||||
unquote(at<name::user>(body)), home_server
|
||||
unquote(at<name::user>(request)), home_server
|
||||
};
|
||||
|
||||
if(!user_id.valid() || user_id.host() != home_server)
|
||||
|
@ -88,14 +92,23 @@ post_login_password(client &client,
|
|||
|
||||
const auto &supplied_password
|
||||
{
|
||||
at<name::password>(body)
|
||||
at<name::password>(request)
|
||||
};
|
||||
|
||||
const auto check{[&supplied_password]
|
||||
// Sets up the query to find the user_id in the accounts room
|
||||
const m::event::where::equal member_event
|
||||
{
|
||||
{ "type", "m.room.member" },
|
||||
{ "state_key", user_id }
|
||||
};
|
||||
|
||||
// Once the query finds the result this closure views the event in the
|
||||
// database and returns true if the login is authentic.
|
||||
const m::event::where::test correct_password{[&supplied_password]
|
||||
(const auto &event)
|
||||
{
|
||||
const json::object &content
|
||||
{
|
||||
{
|
||||
json::val<m::name::content>(event)
|
||||
};
|
||||
|
||||
|
@ -109,56 +122,79 @@ post_login_password(client &client,
|
|||
|
||||
const auto &correct_password
|
||||
{
|
||||
content.at("password")
|
||||
content.get("password")
|
||||
};
|
||||
|
||||
if(!correct_password)
|
||||
return false;
|
||||
|
||||
if(supplied_password != correct_password)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}};
|
||||
|
||||
const m::events::where::equal query
|
||||
const auto query
|
||||
{
|
||||
{ "type", "m.room.member" },
|
||||
{ "state_key", user_id }
|
||||
member_event && correct_password
|
||||
};
|
||||
|
||||
if(!accounts_room.any(query, check))
|
||||
// The query to the database is made here. Know that this ircd::ctx
|
||||
// may suspend and global state may have changed after this call.
|
||||
if(!accounts_room_events.query(query))
|
||||
throw m::error
|
||||
{
|
||||
http::FORBIDDEN, "M_FORBIDDEN", "Access denied."
|
||||
};
|
||||
|
||||
// Generate the access token
|
||||
static constexpr const auto token_len{127};
|
||||
static const auto token_dict{rand::dict::alpha};
|
||||
char token_buf[token_len + 1];
|
||||
const string_view access_token
|
||||
{
|
||||
rand::string(token_dict, token_len, token_buf, sizeof(token_buf))
|
||||
};
|
||||
|
||||
// Log the user in by issuing an event in the accounts room containing
|
||||
// the generated token. When this call completes without throwing the
|
||||
// access_token will be committed and the user will be logged in.
|
||||
accounts_room.send(
|
||||
{
|
||||
{ "type", "ircd.access_token" },
|
||||
{ "sender", user_id },
|
||||
{ "state_key", access_token },
|
||||
{ "content", json::members
|
||||
{
|
||||
{ "ip", string(remote_addr(client)) },
|
||||
{ "device", "unknown" },
|
||||
}}
|
||||
});
|
||||
|
||||
// Send response to user
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ "user_id", string_view{user_id} },
|
||||
{ "home_server", home_server },
|
||||
// { "access_token", access_token },
|
||||
{ "user_id", user_id },
|
||||
{ "home_server", home_server },
|
||||
{ "access_token", access_token },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resource::response
|
||||
post_login(client &client, const resource::request &request)
|
||||
post_login(client &client, const resource::request::object<body> &request)
|
||||
{
|
||||
const resource::request::body<body> body
|
||||
{
|
||||
request
|
||||
};
|
||||
|
||||
// x.x.x Required. The login type being used.
|
||||
// Currently only "m.login.password" is supported.
|
||||
const auto type
|
||||
{
|
||||
unquote(at<name::type>(body))
|
||||
unquote(at<name::type>(request))
|
||||
};
|
||||
|
||||
if(type == "m.login.password")
|
||||
return post_login_password(client, request, body);
|
||||
return post_login_password(client, request);
|
||||
else
|
||||
throw m::error
|
||||
{
|
||||
|
@ -174,14 +210,22 @@ resource::method method_post
|
|||
resource::response
|
||||
get_login(client &client, const resource::request &request)
|
||||
{
|
||||
static const json::object flows
|
||||
json::member login_password
|
||||
{
|
||||
R"({"flows":[{"type":"m.login.password"}]})"
|
||||
"type", "m.login.password"
|
||||
};
|
||||
|
||||
json::value flows[1]
|
||||
{
|
||||
{ login_password }
|
||||
};
|
||||
|
||||
return resource::response
|
||||
{
|
||||
client, flows
|
||||
client, json::members
|
||||
{
|
||||
{ "flows", { flows, 1 } }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -31,18 +31,14 @@ resource logout_resource
|
|||
resource::response
|
||||
logout(client &client, const resource::request &request)
|
||||
{
|
||||
const auto &access_token(request.query.at("access_token"));
|
||||
const auto it(resource::tokens.find(access_token));
|
||||
if(unlikely(it == end(resource::tokens)))
|
||||
throw http::error{http::INTERNAL_SERVER_ERROR};
|
||||
const auto &access_token
|
||||
{
|
||||
request.query.at("access_token")
|
||||
};
|
||||
|
||||
resource::tokens.erase(it);
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ }
|
||||
}
|
||||
client, http::OK
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -33,10 +33,7 @@ get_publicrooms(client &client, const resource::request &request)
|
|||
{
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ }
|
||||
}
|
||||
client, http::OK
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,7 @@ try
|
|||
{
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ }
|
||||
}
|
||||
client, http::OK
|
||||
};
|
||||
}
|
||||
catch(...)
|
||||
|
|
|
@ -27,13 +27,13 @@ const auto home_server
|
|||
"cdc.z"
|
||||
};
|
||||
|
||||
namespace name
|
||||
namespace { namespace name
|
||||
{
|
||||
constexpr auto username{"username"};
|
||||
constexpr auto bind_email{"bind_email"};
|
||||
constexpr auto password{"password"};
|
||||
constexpr auto auth{"auth"};
|
||||
}
|
||||
constexpr const auto username {"username"};
|
||||
constexpr const auto bind_email {"bind_email"};
|
||||
constexpr const auto password {"password"};
|
||||
constexpr const auto auth {"auth"};
|
||||
}}
|
||||
|
||||
struct body
|
||||
:json::tuple
|
||||
|
@ -44,7 +44,7 @@ struct body
|
|||
json::property<name::auth, json::object>
|
||||
>
|
||||
{
|
||||
using super_type::tuple;
|
||||
using super_type::tuple;
|
||||
};
|
||||
|
||||
// Generate !accounts:host which is the MXID of the room where the members
|
||||
|
@ -54,40 +54,26 @@ const m::id::room::buf accounts_room_id
|
|||
"accounts", home_server
|
||||
};
|
||||
|
||||
// This object is a lightweight handle to the accounts room, which is a
|
||||
// chatroom whose members are the representation of the accounts registered
|
||||
// to this server itself.
|
||||
m::room accounts_room
|
||||
{
|
||||
accounts_room_id
|
||||
};
|
||||
|
||||
static void
|
||||
join_accounts_room(const m::id::user &user_id,
|
||||
const json::members &contents)
|
||||
try
|
||||
{
|
||||
const json::builder content
|
||||
{
|
||||
nullptr, &contents
|
||||
};
|
||||
|
||||
accounts_room.join(user_id, content);
|
||||
}
|
||||
catch(const m::ALREADY_MEMBER &e)
|
||||
{
|
||||
throw m::error
|
||||
{
|
||||
http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use."
|
||||
};
|
||||
}
|
||||
static void validate_user_id(const m::id::user &user_id);
|
||||
static void validate_password(const string_view &password);
|
||||
static void join_accounts_room(const m::id::user &user_id, const json::members &contents);
|
||||
|
||||
resource::response
|
||||
handle_post_kind_user(client &client,
|
||||
resource::request &request,
|
||||
const resource::request::body<body> &body)
|
||||
const resource::request::object<body> &request)
|
||||
{
|
||||
// 3.3.1 Additional authentication information for the user-interactive authentication API.
|
||||
const auto &auth
|
||||
{
|
||||
at<name::auth>(body)
|
||||
at<name::auth>(request)
|
||||
};
|
||||
|
||||
// 3.3.1 Required. The login type that the client is attempting to complete.
|
||||
|
@ -96,6 +82,7 @@ handle_post_kind_user(client &client,
|
|||
unquote(auth.at("type"))
|
||||
};
|
||||
|
||||
// We only support this for now, for some reason. TODO: XXX
|
||||
if(type != "m.login.dummy")
|
||||
throw m::error
|
||||
{
|
||||
|
@ -106,53 +93,41 @@ handle_post_kind_user(client &client,
|
|||
// generate a Matrix ID local part.
|
||||
const auto &username
|
||||
{
|
||||
unquote(get<name::username>(body))
|
||||
unquote(get<name::username>(request))
|
||||
};
|
||||
|
||||
// Generate canonical mxid
|
||||
// Generate canonical mxid. The home_server is appended if one is not
|
||||
// specified. We do not generate a user_id here if the local part is not
|
||||
// specified. TODO: isn't that guest reg?
|
||||
const m::id::user::buf user_id
|
||||
{
|
||||
username, home_server
|
||||
};
|
||||
|
||||
if(!user_id.valid())
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_USERNAME", "The desired user ID is not a valid user name."
|
||||
};
|
||||
|
||||
if(user_id.host() != home_server)
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_USERNAME", "Can only register with host '%s'", home_server
|
||||
};
|
||||
// Check if the the user_id is acceptably formed for this server or throws
|
||||
validate_user_id(user_id);
|
||||
|
||||
// 3.3.1 Required. The desired password for the account.
|
||||
const auto &password
|
||||
{
|
||||
at<name::password>(body)
|
||||
at<name::password>(request)
|
||||
};
|
||||
|
||||
if(password.size() > 255)
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_PASSWORD", "The desired password is too long"
|
||||
};
|
||||
|
||||
// 3.3.1 If true, the server binds the email used for authentication to the
|
||||
// Matrix ID with the ID Server. Defaults to false.
|
||||
const auto &bind_email
|
||||
{
|
||||
get<name::bind_email>(body, false)
|
||||
get<name::bind_email>(request, false)
|
||||
};
|
||||
|
||||
// Register the user by joining them to the accounts room. Join the user
|
||||
// to the accounts room by issuing a join event. The content will store
|
||||
// keys from the registration options including the password - do not
|
||||
// expose this to clients //TODO: store hashed pass
|
||||
// Once this call completes the join was successful and the user
|
||||
// is registered, otherwise throws.
|
||||
char content[384];
|
||||
// Check if the password is acceptable for this server or throws
|
||||
validate_password(password);
|
||||
|
||||
// Register the user by joining them to the accounts room. The content of
|
||||
// the join event will store keys from the registration options including
|
||||
// the password - do not expose this to clients //TODO: store hashed pass
|
||||
// Once this call completes the join was successful and the user is
|
||||
// registered, otherwise throws.
|
||||
join_accounts_room(user_id,
|
||||
{
|
||||
{ "password", password },
|
||||
|
@ -173,8 +148,7 @@ handle_post_kind_user(client &client,
|
|||
|
||||
resource::response
|
||||
handle_post_kind_guest(client &client,
|
||||
resource::request &request,
|
||||
const resource::request::body<body> &body)
|
||||
const resource::request::object<body> &request)
|
||||
{
|
||||
const m::id::user::buf user_id
|
||||
{
|
||||
|
@ -187,30 +161,25 @@ handle_post_kind_guest(client &client,
|
|||
{
|
||||
{ "user_id", user_id },
|
||||
{ "home_server", home_server },
|
||||
//{ "access_token", access_token },
|
||||
// { "access_token", access_token },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resource::response
|
||||
handle_post(client &client,
|
||||
resource::request &request)
|
||||
const resource::request::object<body> &request)
|
||||
{
|
||||
const resource::request::body<body> body
|
||||
{
|
||||
request
|
||||
};
|
||||
|
||||
const auto kind
|
||||
{
|
||||
request.query["kind"]
|
||||
};
|
||||
|
||||
if(kind == "user")
|
||||
return handle_post_kind_user(client, request, body);
|
||||
return handle_post_kind_user(client, request);
|
||||
|
||||
if(kind.empty() || kind == "guest")
|
||||
return handle_post_kind_guest(client, request, body);
|
||||
return handle_post_kind_guest(client, request);
|
||||
|
||||
throw m::error
|
||||
{
|
||||
|
@ -233,3 +202,51 @@ mapi::header IRCD_MODULE
|
|||
{
|
||||
"registers the resource 'client/register' to handle requests"
|
||||
};
|
||||
|
||||
void
|
||||
join_accounts_room(const m::id::user &user_id,
|
||||
const json::members &contents)
|
||||
try
|
||||
{
|
||||
json::iov content;
|
||||
json::iov::push members[contents.size()];
|
||||
|
||||
size_t i(0);
|
||||
for(const auto &member : contents)
|
||||
new (members + i++) json::iov::push(content, member);
|
||||
|
||||
accounts_room.join(user_id, content);
|
||||
}
|
||||
catch(const m::ALREADY_MEMBER &e)
|
||||
{
|
||||
throw m::error
|
||||
{
|
||||
http::CONFLICT, "M_USER_IN_USE", "The desired user ID is already in use."
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
validate_user_id(const m::id::user &user_id)
|
||||
{
|
||||
if(!user_id.valid())
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_USERNAME", "The desired user ID is not a valid user name."
|
||||
};
|
||||
|
||||
if(user_id.host() != home_server)
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_USERNAME", "Can only register with host '%s'", home_server
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
validate_password(const string_view &password)
|
||||
{
|
||||
if(password.size() > 255)
|
||||
throw m::error
|
||||
{
|
||||
"M_INVALID_PASSWORD", "The desired password is too long"
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,15 +24,188 @@ using namespace ircd;
|
|||
struct room
|
||||
:resource
|
||||
{
|
||||
static constexpr const auto base_url
|
||||
{
|
||||
"_matrix/client/r0/rooms/"
|
||||
};
|
||||
|
||||
using resource::resource;
|
||||
}
|
||||
rooms_resource
|
||||
{
|
||||
"_matrix/client/r0/rooms/",
|
||||
"Rooms (7.0)"
|
||||
room::base_url, resource::opts
|
||||
{
|
||||
resource::DIRECTORY,
|
||||
"Rooms (7.0)"
|
||||
}
|
||||
};
|
||||
|
||||
mapi::header IRCD_MODULE
|
||||
{
|
||||
"registers the resource 'client/rooms'"
|
||||
};
|
||||
|
||||
resource::response
|
||||
get_state(client &client,
|
||||
const resource::request &request,
|
||||
const string_view ¶ms,
|
||||
const m::room::state &state,
|
||||
const m::event::where &query)
|
||||
{
|
||||
const auto count
|
||||
{
|
||||
state.count(query)
|
||||
};
|
||||
|
||||
size_t j(0);
|
||||
json::value ret[count];
|
||||
state.for_each(query, [&count, &j, &ret]
|
||||
(const auto &event)
|
||||
{
|
||||
if(j < count)
|
||||
ret[j++] = event;
|
||||
});
|
||||
|
||||
return resource::response
|
||||
{
|
||||
client, json::value
|
||||
{
|
||||
ret, j
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resource::response
|
||||
get_state(client &client,
|
||||
const resource::request &request,
|
||||
const string_view ¶ms,
|
||||
const m::room::state &state,
|
||||
const string_view &type,
|
||||
const string_view &state_key)
|
||||
{
|
||||
const m::event::where::equal query
|
||||
{
|
||||
{ "type", type },
|
||||
{ "state_key", state_key },
|
||||
};
|
||||
|
||||
return get_state(client, request, params, state, query);
|
||||
}
|
||||
|
||||
resource::response
|
||||
get_state(client &client,
|
||||
const resource::request &request,
|
||||
const string_view ¶ms,
|
||||
const m::room::state &state,
|
||||
const string_view &type)
|
||||
{
|
||||
const m::event::where::equal query
|
||||
{
|
||||
{ "type", type }
|
||||
};
|
||||
|
||||
return get_state(client, request, params, state, query);
|
||||
}
|
||||
|
||||
resource::response
|
||||
get_state(client &client,
|
||||
const resource::request &request,
|
||||
const string_view ¶ms,
|
||||
const m::room::id &room_id)
|
||||
{
|
||||
string_view token[4];
|
||||
tokens(params, "/", token);
|
||||
|
||||
const string_view &type
|
||||
{
|
||||
token[2]
|
||||
};
|
||||
|
||||
const string_view &state_key
|
||||
{
|
||||
token[3]
|
||||
};
|
||||
|
||||
const m::room::state state
|
||||
{
|
||||
room_id
|
||||
};
|
||||
|
||||
if(type && state_key)
|
||||
return get_state(client, request, params, state, type, state_key);
|
||||
|
||||
if(type)
|
||||
return get_state(client, request, params, state, type);
|
||||
|
||||
const m::event::where::noop query;
|
||||
return get_state(client, request, params, state, query);
|
||||
}
|
||||
|
||||
resource::response
|
||||
get_context(client &client,
|
||||
const resource::request &request,
|
||||
const string_view ¶ms,
|
||||
const m::room::id &room_id)
|
||||
{
|
||||
const m::event::id &event_id
|
||||
{
|
||||
token(params, "/", 2)
|
||||
};
|
||||
|
||||
const auto it
|
||||
{
|
||||
m::event::find(event_id)
|
||||
};
|
||||
|
||||
if(!it)
|
||||
throw http::error(http::NOT_FOUND, "event not found");
|
||||
|
||||
const auto event
|
||||
{
|
||||
json::string(*it)
|
||||
};
|
||||
|
||||
return resource::response
|
||||
{
|
||||
client, json::members
|
||||
{
|
||||
{ "event", event }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resource::response
|
||||
get_rooms(client &client, const resource::request &request)
|
||||
{
|
||||
const auto params
|
||||
{
|
||||
lstrip(request.head.path, room::base_url)
|
||||
};
|
||||
|
||||
string_view token[2];
|
||||
if(tokens(params, "/", token) != 2)
|
||||
throw http::error(http::code::NOT_FOUND, "/rooms command required");
|
||||
|
||||
const m::room::id &room_id
|
||||
{
|
||||
token[0]
|
||||
};
|
||||
|
||||
const string_view &cmd
|
||||
{
|
||||
token[1]
|
||||
};
|
||||
|
||||
if(cmd == "context")
|
||||
return get_context(client, request, params, room_id);
|
||||
|
||||
if(cmd == "state")
|
||||
return get_state(client, request, params, room_id);
|
||||
|
||||
throw http::error(http::code::NOT_FOUND, "/rooms command not found");
|
||||
}
|
||||
|
||||
resource::method method_get
|
||||
{
|
||||
rooms_resource, "GET", get_rooms
|
||||
};
|
||||
|
|
|
@ -26,13 +26,27 @@ resource sync_resource
|
|||
"_matrix/client/r0/sync",
|
||||
|
||||
R"(
|
||||
Synchronise the client's state with the latest state on the server.
|
||||
6.2. Synchronise the client's state with the latest state on the server.
|
||||
Clients use this API when they first log in to get an initial snapshot of
|
||||
the state on the server, and then continue to call this API to get
|
||||
incremental deltas to the state, and to receive new messages. (6.2)
|
||||
incremental deltas to the state, and to receive new messages.
|
||||
)"
|
||||
};
|
||||
|
||||
struct polldata
|
||||
{
|
||||
std::weak_ptr<ircd::client> client;
|
||||
steady_point timeout;
|
||||
};
|
||||
|
||||
std::deque<polldata>
|
||||
polling
|
||||
{};
|
||||
|
||||
ircd::ctx::dock
|
||||
polldock
|
||||
{};
|
||||
|
||||
resource::response
|
||||
sync(client &client, const resource::request &request)
|
||||
{
|
||||
|
@ -72,12 +86,22 @@ sync(client &client, const resource::request &request)
|
|||
request.get("set_presence", "offline")
|
||||
};
|
||||
|
||||
const auto timeout
|
||||
const milliseconds timeout
|
||||
{
|
||||
// 6.2.1 The maximum time to poll in milliseconds before returning this request.
|
||||
request.get<time_t>("timeout", -1)
|
||||
request.get<time_t>("timeout", 30 * 1000)
|
||||
};
|
||||
|
||||
// A reference to the client is saved. We save a weak reference to still
|
||||
// allow the client to disconnect.
|
||||
polling.emplace_back(polldata
|
||||
{
|
||||
weak_from(client),
|
||||
now<steady_point>() + timeout
|
||||
});
|
||||
|
||||
// This handler returns no response. As long as this handler doesn't throw
|
||||
// an exception IRCd will keep the client alive.
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -89,7 +113,81 @@ resource::method get_sync
|
|||
}
|
||||
};
|
||||
|
||||
void worker();
|
||||
ircd::context synchronizer_context
|
||||
{
|
||||
"synchronizer",
|
||||
1_MiB,
|
||||
&worker,
|
||||
ircd::context::POST,
|
||||
};
|
||||
|
||||
const auto on_unload{[]
|
||||
{
|
||||
synchronizer_context.interrupt();
|
||||
synchronizer_context.join();
|
||||
}};
|
||||
|
||||
mapi::header IRCD_MODULE
|
||||
{
|
||||
"registers the resource 'client/sync' to handle requests."
|
||||
"registers the resource 'client/sync' to handle requests.",
|
||||
nullptr,
|
||||
on_unload
|
||||
};
|
||||
|
||||
void
|
||||
handle_event(const m::event &event,
|
||||
const polldata &request)
|
||||
try
|
||||
{
|
||||
const life_guard<client> client
|
||||
{
|
||||
request.client
|
||||
};
|
||||
|
||||
resource::response
|
||||
{
|
||||
*client, json::members
|
||||
{
|
||||
{ "event", json::string(event) }
|
||||
}
|
||||
};
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::error("%s", e.what());
|
||||
}
|
||||
|
||||
void
|
||||
synchronize(const m::event &event)
|
||||
{
|
||||
if(polling.empty())
|
||||
return;
|
||||
|
||||
const auto &request
|
||||
{
|
||||
polling.front()
|
||||
};
|
||||
|
||||
handle_event(event, request);
|
||||
polling.pop_front();
|
||||
}
|
||||
|
||||
void
|
||||
worker()
|
||||
try
|
||||
{
|
||||
for(;; ctx::interruption_point())
|
||||
{
|
||||
const auto &event
|
||||
{
|
||||
m::event::inserted.wait()
|
||||
};
|
||||
|
||||
synchronize(event);
|
||||
}
|
||||
}
|
||||
catch(const ircd::ctx::interrupted &e)
|
||||
{
|
||||
ircd::log::debug("Synchronizer interrupted");
|
||||
}
|
||||
|
|
|
@ -30,35 +30,58 @@ resource user_resource
|
|||
}
|
||||
};
|
||||
|
||||
namespace ircd
|
||||
m::room filters_room
|
||||
{
|
||||
static string_view extract_user_id(const auto &path, const auto &suffix);
|
||||
}
|
||||
m::room::id{"!filters:cdc.z"}
|
||||
};
|
||||
|
||||
ircd::string_view
|
||||
ircd::extract_user_id(const auto &path,
|
||||
const auto &suffix)
|
||||
const m::room::events filters_room_events
|
||||
{
|
||||
// Extract the user_id from _matrix/client/r0/user/$user/filter
|
||||
return lstrip(between(path, user_resource.path, suffix), '/');
|
||||
}
|
||||
filters_room
|
||||
};
|
||||
|
||||
resource::response
|
||||
get_filter(client &client, const resource::request &request)
|
||||
try
|
||||
{
|
||||
const auto user_id
|
||||
const m::user::id user_id
|
||||
{
|
||||
extract_user_id(request.head.path, "/filter")
|
||||
token(request.head.path, '/', 4)
|
||||
};
|
||||
|
||||
return resource::response
|
||||
const auto filter_id
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ }
|
||||
}
|
||||
token(request.head.path, '/', 6)
|
||||
};
|
||||
|
||||
const m::event::where::equal query
|
||||
{
|
||||
{ "type", "ircd.filter" },
|
||||
{ "sender", user_id },
|
||||
{ "state_key", filter_id }
|
||||
};
|
||||
|
||||
const auto result{[&client]
|
||||
(const m::event &event)
|
||||
{
|
||||
const json::object &filter
|
||||
{
|
||||
json::at<m::name::content>(event)
|
||||
};
|
||||
|
||||
resource::response
|
||||
{
|
||||
client, filter
|
||||
};
|
||||
|
||||
return true;
|
||||
}};
|
||||
|
||||
if(!filters_room_events.any(query, result))
|
||||
throw m::NOT_FOUND("No matching filter with that ID");
|
||||
|
||||
// Response already made
|
||||
return {};
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -67,65 +90,88 @@ catch(...)
|
|||
|
||||
resource::method get
|
||||
{
|
||||
user_resource, "GET", get_filter
|
||||
user_resource, "GET", get_filter,
|
||||
{
|
||||
resource::method::REQUIRES_AUTH
|
||||
}
|
||||
};
|
||||
|
||||
// (5.2) Uploads a new filter definition to the homeserver. Returns a filter ID that
|
||||
// may be used in future requests to restrict which events are returned to the client.
|
||||
resource::response
|
||||
post_filter(client &client, const resource::request &request)
|
||||
post_filter(client &client, const resource::request::object<const m::filter> &request)
|
||||
try
|
||||
{
|
||||
const auto user_id
|
||||
const auto &user_id
|
||||
{
|
||||
// (5.2) Required. The id of the user uploading the filter. The access
|
||||
// token must be authorized to make requests for this user id.
|
||||
extract_user_id(request.head.path, "/filter")
|
||||
token(request.head.path, '/', 4)
|
||||
};
|
||||
|
||||
const auto event_fields
|
||||
const auto &event_fields
|
||||
{
|
||||
// (5.2) List of event fields to include. If this list is absent then all fields are
|
||||
// included. The entries may include '.' charaters to indicate sub-fields. So
|
||||
// ['content.body'] will include the 'body' field of the 'content' object. A literal '.'
|
||||
// character in a field name may be escaped using a '\'. A server may include more
|
||||
// fields than were requested.
|
||||
request["event_fields"]
|
||||
json::get<m::name::event_fields>(request)
|
||||
};
|
||||
|
||||
const auto event_format
|
||||
const auto &event_format
|
||||
{
|
||||
// (5.2) The format to use for events. 'client' will return the events in a format suitable
|
||||
// for clients. 'federation' will return the raw event as receieved over federation.
|
||||
// The default is 'client'. One of: ["client", "federation"]
|
||||
request["event_format"]
|
||||
json::get<m::name::event_format>(request)
|
||||
};
|
||||
|
||||
const auto account_data
|
||||
const auto &account_data
|
||||
{
|
||||
// (5.2) The user account data that isn't associated with rooms to include.
|
||||
request["account_data"]
|
||||
json::val<m::name::account_data>(request)
|
||||
};
|
||||
|
||||
const auto room
|
||||
const auto &room
|
||||
{
|
||||
// (5.2) Filters to be applied to room data.
|
||||
request["room"]
|
||||
json::val<m::name::room>(request)
|
||||
};
|
||||
|
||||
const auto presence
|
||||
const auto &state
|
||||
{
|
||||
json::val<m::name::state>(room)
|
||||
};
|
||||
|
||||
std::cout << json::get<m::name::limit>(state) << std::endl;
|
||||
|
||||
const auto &presence
|
||||
{
|
||||
// (5.2) The presence updates to include.
|
||||
request["presence"]
|
||||
json::val<m::name::presence>(request)
|
||||
};
|
||||
|
||||
std::cout << "do filter: " << request.content << std::endl;
|
||||
const auto filter_id
|
||||
{
|
||||
"abc123"
|
||||
};
|
||||
|
||||
json::iov event;
|
||||
json::iov::push members[]
|
||||
{
|
||||
{ event, json::member { "type", "ircd.filter" }},
|
||||
{ event, json::member { "state_key", filter_id }},
|
||||
{ event, json::member { "sender", user_id }},
|
||||
{ event, json::member { "content", request.body }}
|
||||
};
|
||||
|
||||
filters_room.send(event);
|
||||
return resource::response
|
||||
{
|
||||
client, http::CREATED,
|
||||
{
|
||||
{ "filter_id", "abc321" }
|
||||
{ "filter_id", filter_id }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -136,7 +182,10 @@ catch(...)
|
|||
|
||||
resource::method post
|
||||
{
|
||||
user_resource, "POST", post_filter
|
||||
user_resource, "POST", post_filter,
|
||||
{
|
||||
post.REQUIRES_AUTH
|
||||
}
|
||||
};
|
||||
|
||||
mapi::header IRCD_MODULE
|
||||
|
|
|
@ -38,10 +38,7 @@ get_turnserver(client &client, const resource::request &request)
|
|||
{
|
||||
return resource::response
|
||||
{
|
||||
client,
|
||||
{
|
||||
{ },
|
||||
}
|
||||
client, http::OK
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -278,10 +278,58 @@ const database::descriptor events_signatures_descriptor
|
|||
}
|
||||
};
|
||||
|
||||
const database::descriptor index_room_id_to_event_id
|
||||
/// prefix transform for event_id suffixes
|
||||
///
|
||||
/// This transform expects a concatenation ending with an event_id which means
|
||||
/// the prefix can be the same for multiple event_id's; therefor we can find
|
||||
/// or iterate "event_id in X" where X is some key like a room_id
|
||||
///
|
||||
const ircd::db::prefix_transform event_id_in
|
||||
{
|
||||
"event_id in",
|
||||
[](const string_view &key)
|
||||
{
|
||||
return key.find('$') != key.npos;
|
||||
},
|
||||
[](const string_view &key)
|
||||
{
|
||||
return rsplit(key, '$').first;
|
||||
}
|
||||
};
|
||||
|
||||
const database::descriptor event_id_in_sender
|
||||
{
|
||||
// name
|
||||
"!room_id$event_id",
|
||||
"event_id in sender",
|
||||
|
||||
// explanation
|
||||
R"(### developer note:
|
||||
|
||||
key is "@sender$event_id"
|
||||
the prefix transform is in effect. this column indexes events by
|
||||
sender offering an iterable bound of the index prefixed by sender
|
||||
|
||||
)",
|
||||
|
||||
// typing (key, value)
|
||||
{
|
||||
typeid(string_view), typeid(string_view)
|
||||
},
|
||||
|
||||
// options
|
||||
{},
|
||||
|
||||
// comparator
|
||||
{},
|
||||
|
||||
// prefix transform
|
||||
event_id_in,
|
||||
};
|
||||
|
||||
const database::descriptor event_id_in_room_id
|
||||
{
|
||||
// name
|
||||
"event_id in room_id",
|
||||
|
||||
// explanation
|
||||
R"(### developer note:
|
||||
|
@ -300,27 +348,99 @@ const database::descriptor index_room_id_to_event_id
|
|||
// options
|
||||
{},
|
||||
|
||||
// comparator - sorts from highest to lowest
|
||||
ircd::db::reverse_cmp_string_view{},
|
||||
|
||||
// prefix transform
|
||||
event_id_in,
|
||||
};
|
||||
|
||||
/// prefix transform for room_id
|
||||
///
|
||||
/// This transform expects a concatenation ending with a room_id which means
|
||||
/// the prefix can be the same for multiple room_id's; therefor we can find
|
||||
/// or iterate "room_id in X" where X is some repeated prefix
|
||||
///
|
||||
const ircd::db::prefix_transform room_id_in
|
||||
{
|
||||
"room_id in",
|
||||
[](const string_view &key)
|
||||
{
|
||||
return key.find('!') != key.npos;
|
||||
},
|
||||
[](const string_view &key)
|
||||
{
|
||||
return rsplit(key, '!').first;
|
||||
}
|
||||
};
|
||||
|
||||
const database::descriptor event_id_for_room_id_in_type
|
||||
{
|
||||
// name
|
||||
"event_id for room_id in type",
|
||||
|
||||
// explanation
|
||||
R"(### developer note:
|
||||
|
||||
)",
|
||||
|
||||
// typing (key, value)
|
||||
{
|
||||
typeid(string_view), typeid(string_view)
|
||||
},
|
||||
|
||||
// options
|
||||
{},
|
||||
|
||||
// comparator
|
||||
{},
|
||||
|
||||
// prefix transform
|
||||
room_id_in,
|
||||
};
|
||||
|
||||
/// prefix transform for type,state_key in room_id
|
||||
///
|
||||
/// This transform is special for concatenating room_id with type and state_key
|
||||
/// in that order with prefix being the room_id (this may change to room_id+
|
||||
/// type
|
||||
///
|
||||
const ircd::db::prefix_transform type_state_key_in_room_id
|
||||
{
|
||||
"type,state_key in room_id",
|
||||
[](const string_view &key)
|
||||
{
|
||||
"!room_id$event_id"s,
|
||||
[](const string_view &key)
|
||||
{
|
||||
return key.find('$') != key.npos;
|
||||
},
|
||||
[](const string_view &key)
|
||||
{
|
||||
return split(key, '$').first;
|
||||
}
|
||||
return key.find("..") != key.npos;
|
||||
},
|
||||
[](const string_view &key)
|
||||
{
|
||||
return split(key, "..").first;
|
||||
}
|
||||
};
|
||||
|
||||
// hooks
|
||||
// {
|
||||
const database::descriptor event_id_for_type_state_key_in_room_id
|
||||
{
|
||||
// name
|
||||
"event_id for type,state_key in room_id",
|
||||
|
||||
// explanation
|
||||
R"(### developer note:
|
||||
|
||||
// }
|
||||
)",
|
||||
|
||||
// typing (key, value)
|
||||
{
|
||||
typeid(string_view), typeid(string_view)
|
||||
},
|
||||
|
||||
// options
|
||||
{},
|
||||
|
||||
// comparator
|
||||
{},
|
||||
|
||||
// prefix transform
|
||||
type_state_key_in_room_id
|
||||
};
|
||||
|
||||
const database::description events_description
|
||||
|
@ -337,7 +457,10 @@ const database::description events_description
|
|||
events_prev_ids_descriptor,
|
||||
events_unsigned_descriptor,
|
||||
events_signatures_descriptor,
|
||||
index_room_id_to_event_id,
|
||||
event_id_in_sender,
|
||||
event_id_in_room_id,
|
||||
event_id_for_room_id_in_type,
|
||||
event_id_for_type_state_key_in_room_id,
|
||||
};
|
||||
|
||||
std::shared_ptr<database> events_database
|
||||
|
|
Loading…
Reference in a new issue