mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-07 06:53:09 -06:00
Create the initial set of state events for room creation
This includes authing the resulting events.
This commit is contained in:
parent
533e5dba5a
commit
6ca3630455
|
|
@ -5,6 +5,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/config"
|
||||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
@ -36,6 +39,16 @@ func main() {
|
||||||
setupLogging(logDir)
|
setupLogging(logDir)
|
||||||
}
|
}
|
||||||
log.Info("Starting clientapi")
|
log.Info("Starting clientapi")
|
||||||
routing.Setup(http.DefaultServeMux, http.DefaultClient)
|
// TODO: Rather than generating a new key on every startup, we should be
|
||||||
|
// reading a PEM formatted file instead.
|
||||||
|
_, privKey, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Failed to generate private key: %s", err)
|
||||||
|
}
|
||||||
|
routing.Setup(http.DefaultServeMux, http.DefaultClient, config.ClientAPI{
|
||||||
|
ServerName: "localhost",
|
||||||
|
KeyID: "ed25519:something",
|
||||||
|
PrivateKey: privKey,
|
||||||
|
})
|
||||||
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,15 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StateTuple is the tuple of an event type and an event state_key, typically used as a key
|
||||||
|
// in maps.
|
||||||
|
type StateTuple struct {
|
||||||
|
// Type is the event type e.g "m.room.name"
|
||||||
|
Type string
|
||||||
|
// Key is the state key e.g. ""
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSONRequest into the given interface pointer. Returns an error JSON response if
|
// UnmarshalJSONRequest into the given interface pointer. Returns an error JSON response if
|
||||||
// there was a problem unmarshalling. Calling this function consumes the request body.
|
// there was a problem unmarshalling. Calling this function consumes the request body.
|
||||||
func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONResponse {
|
func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
// CreateContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create
|
||||||
|
type CreateContent struct {
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
Federate *bool `json:"m.federate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemberContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member
|
||||||
|
type MemberContent struct {
|
||||||
|
Membership string `json:"membership"`
|
||||||
|
DisplayName string `json:"displayname,omitempty"`
|
||||||
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
|
// TODO: ThirdPartyInvite string `json:"third_party_invite,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinRulesContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules
|
||||||
|
type JoinRulesContent struct {
|
||||||
|
JoinRule string `json:"join_rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistoryVisibilityContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-history-visibility
|
||||||
|
type HistoryVisibilityContent struct {
|
||||||
|
HistoryVisibility string `json:"history_visibility"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowerLevelContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels
|
||||||
|
type PowerLevelContent struct {
|
||||||
|
EventsDefault int `json:"events_default"`
|
||||||
|
Invite int `json:"invite"`
|
||||||
|
StateDefault int `json:"state_default"`
|
||||||
|
Redact int `json:"redact"`
|
||||||
|
Ban int `json:"ban"`
|
||||||
|
UsersDefault int `json:"users_default"`
|
||||||
|
Events map[string]int `json:"events"`
|
||||||
|
Kick int `json:"kick"`
|
||||||
|
Users map[string]int `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitialPowerLevelsContent returns the initial values for m.room.power_levels on room creation
|
||||||
|
// if they have not been specified.
|
||||||
|
// http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels
|
||||||
|
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L294
|
||||||
|
func InitialPowerLevelsContent(roomCreator string) PowerLevelContent {
|
||||||
|
return PowerLevelContent{
|
||||||
|
EventsDefault: 0,
|
||||||
|
Invite: 0,
|
||||||
|
StateDefault: 50,
|
||||||
|
Redact: 50,
|
||||||
|
Ban: 50,
|
||||||
|
UsersDefault: 0,
|
||||||
|
Events: map[string]int{
|
||||||
|
"m.room.name": 50,
|
||||||
|
"m.room.power_levels": 100,
|
||||||
|
"m.room.history_visibility": 100,
|
||||||
|
"m.room.canonical_alias": 50,
|
||||||
|
"m.room.avatar": 50,
|
||||||
|
},
|
||||||
|
Kick: 50,
|
||||||
|
Users: map[string]int{roomCreator: 100},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
// ClientAPI contains the config information necessary to spin up a clientapi process.
|
||||||
|
type ClientAPI struct {
|
||||||
|
ServerName string
|
||||||
|
PrivateKey ed25519.PrivateKey
|
||||||
|
KeyID string
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/config"
|
||||||
"github.com/matrix-org/dendrite/clientapi/readers"
|
"github.com/matrix-org/dendrite/clientapi/readers"
|
||||||
"github.com/matrix-org/dendrite/clientapi/writers"
|
"github.com/matrix-org/dendrite/clientapi/writers"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -14,17 +15,17 @@ const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
|
|
||||||
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
||||||
// to clients which need to make outbound HTTP requests.
|
// to clients which need to make outbound HTTP requests.
|
||||||
func Setup(servMux *http.ServeMux, httpClient *http.Client) {
|
func Setup(servMux *http.ServeMux, httpClient *http.Client, cfg config.ClientAPI) {
|
||||||
apiMux := mux.NewRouter()
|
apiMux := mux.NewRouter()
|
||||||
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
r0mux.Handle("/createRoom", make("createRoom", wrap(func(req *http.Request) util.JSONResponse {
|
r0mux.Handle("/createRoom", make("createRoom", util.NewJSONRequestHandler(func(req *http.Request) util.JSONResponse {
|
||||||
return writers.CreateRoom(req)
|
return writers.CreateRoom(req, cfg)
|
||||||
})))
|
})))
|
||||||
r0mux.Handle("/sync", make("sync", wrap(func(req *http.Request) util.JSONResponse {
|
r0mux.Handle("/sync", make("sync", util.NewJSONRequestHandler(func(req *http.Request) util.JSONResponse {
|
||||||
return readers.Sync(req)
|
return readers.Sync(req)
|
||||||
})))
|
})))
|
||||||
r0mux.Handle("/rooms/{roomID}/send/{eventType}",
|
r0mux.Handle("/rooms/{roomID}/send/{eventType}",
|
||||||
make("send_message", wrap(func(req *http.Request) util.JSONResponse {
|
make("send_message", util.NewJSONRequestHandler(func(req *http.Request) util.JSONResponse {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
return writers.SendMessage(req, vars["roomID"], vars["eventType"])
|
return writers.SendMessage(req, vars["roomID"], vars["eventType"])
|
||||||
})),
|
})),
|
||||||
|
|
@ -38,15 +39,3 @@ func Setup(servMux *http.ServeMux, httpClient *http.Client) {
|
||||||
func make(metricsName string, h util.JSONRequestHandler) http.Handler {
|
func make(metricsName string, h util.JSONRequestHandler) http.Handler {
|
||||||
return prometheus.InstrumentHandler(metricsName, util.MakeJSONAPI(h))
|
return prometheus.InstrumentHandler(metricsName, util.MakeJSONAPI(h))
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonRequestHandlerWrapper is a wrapper to allow in-line functions to conform to util.JSONRequestHandler
|
|
||||||
type jsonRequestHandlerWrapper struct {
|
|
||||||
function func(req *http.Request) util.JSONResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *jsonRequestHandlerWrapper) OnIncomingRequest(req *http.Request) util.JSONResponse {
|
|
||||||
return r.function(req)
|
|
||||||
}
|
|
||||||
func wrap(f func(req *http.Request) util.JSONResponse) *jsonRequestHandlerWrapper {
|
|
||||||
return &jsonRequestHandlerWrapper{f}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/common"
|
"github.com/matrix-org/dendrite/clientapi/common"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/config"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,17 +67,23 @@ type createRoomResponse struct {
|
||||||
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fledglingEvent is a helper representation of an event used when creating many events in succession.
|
||||||
|
type fledglingEvent struct {
|
||||||
|
Type string
|
||||||
|
StateKey *string
|
||||||
|
Content interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRoom implements /createRoom
|
// CreateRoom implements /createRoom
|
||||||
func CreateRoom(req *http.Request) util.JSONResponse {
|
func CreateRoom(req *http.Request, cfg config.ClientAPI) util.JSONResponse {
|
||||||
serverName := "localhost"
|
|
||||||
// TODO: Check room ID doesn't clash with an existing one, and we
|
// TODO: Check room ID doesn't clash with an existing one, and we
|
||||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||||
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), serverName)
|
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), cfg.ServerName)
|
||||||
return createRoom(req, roomID)
|
return createRoom(req, cfg, roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRoom implements /createRoom
|
// createRoom implements /createRoom
|
||||||
func createRoom(req *http.Request, roomID string) util.JSONResponse {
|
func createRoom(req *http.Request, cfg config.ClientAPI, roomID string) util.JSONResponse {
|
||||||
logger := util.GetLogger(req.Context())
|
logger := util.GetLogger(req.Context())
|
||||||
userID, resErr := auth.VerifyAccessToken(req)
|
userID, resErr := auth.VerifyAccessToken(req)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
|
|
@ -98,7 +107,11 @@ func createRoom(req *http.Request, roomID string) util.JSONResponse {
|
||||||
logger.WithFields(log.Fields{
|
logger.WithFields(log.Fields{
|
||||||
"userID": userID,
|
"userID": userID,
|
||||||
"roomID": roomID,
|
"roomID": roomID,
|
||||||
}).Info("Creating room")
|
}).Info("Creating new room")
|
||||||
|
|
||||||
|
// Remember events we've built and key off the state tuple so we can look them up easily when filling in auth_events
|
||||||
|
builtEventMap := make(map[common.StateTuple]*gomatrixserverlib.Event)
|
||||||
|
var builtEvents []*gomatrixserverlib.Event
|
||||||
|
|
||||||
// send events into the room in order of:
|
// send events into the room in order of:
|
||||||
// 1- m.room.create
|
// 1- m.room.create
|
||||||
|
|
@ -117,12 +130,147 @@ func createRoom(req *http.Request, roomID string) util.JSONResponse {
|
||||||
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
||||||
// depending on if those events were in "initial_state" or not. This made it
|
// depending on if those events were in "initial_state" or not. This made it
|
||||||
// harder to reason about, hence sticking to a strict static ordering.
|
// harder to reason about, hence sticking to a strict static ordering.
|
||||||
|
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||||
// f.e event:
|
eventsToMake := []fledglingEvent{
|
||||||
// - validate required keys/types (EventValidator in synapse)
|
{"m.room.create", emptyString(), common.CreateContent{Creator: userID}},
|
||||||
// - set additional keys (displayname/avatar_url for m.room.member)
|
{"m.room.member", &userID, common.MemberContent{Membership: "join"}}, // TODO: Set avatar_url / displayname
|
||||||
// - set token(?) and txn id
|
{"m.room.power_levels", emptyString(), common.InitialPowerLevelsContent(userID)},
|
||||||
// - then https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/message.py#L419
|
// TODO: m.room.canonical_alias
|
||||||
|
{"m.room.join_rules", emptyString(), common.JoinRulesContent{"public"}}, // FIXME: Allow this to be changed
|
||||||
return util.MessageResponse(404, "Not implemented yet")
|
{"m.room.history_visibility", emptyString(), common.HistoryVisibilityContent{"joined"}}, // FIXME: Allow this to be changed
|
||||||
|
// TODO: m.room.guest_access
|
||||||
|
// TODO: Other initial state items
|
||||||
|
// TODO: m.room.name
|
||||||
|
// TODO: m.room.topic
|
||||||
|
// TODO: invite events
|
||||||
|
// TODO: 3pid invite events
|
||||||
|
// TODO m.room.aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, e := range eventsToMake {
|
||||||
|
depth := i + 1 // depth starts at 1
|
||||||
|
|
||||||
|
builder := gomatrixserverlib.EventBuilder{
|
||||||
|
Sender: userID,
|
||||||
|
RoomID: roomID,
|
||||||
|
Type: e.Type,
|
||||||
|
StateKey: e.StateKey,
|
||||||
|
Depth: int64(depth),
|
||||||
|
}
|
||||||
|
builder.SetContent(e.Content)
|
||||||
|
ev, err := buildEvent(&builder, builtEventMap, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
builtEventMap[common.StateTuple{e.Type, *e.StateKey}] = ev
|
||||||
|
builtEvents = append(builtEvents, ev)
|
||||||
|
}
|
||||||
|
authEvents := authEventProvider{builtEventMap}
|
||||||
|
|
||||||
|
// auth each event in turn
|
||||||
|
for _, e := range builtEvents {
|
||||||
|
if err := gomatrixserverlib.Allowed(*e, &authEvents); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: builtEvents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildEvent fills out auth_events for the builder then builds the event
|
||||||
|
func buildEvent(builder *gomatrixserverlib.EventBuilder,
|
||||||
|
events map[common.StateTuple]*gomatrixserverlib.Event,
|
||||||
|
cfg config.ClientAPI) (*gomatrixserverlib.Event, error) {
|
||||||
|
|
||||||
|
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builder.AuthEvents = authEventsFromStateNeeded(eventsNeeded, events)
|
||||||
|
eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.ServerName)
|
||||||
|
now := time.Now()
|
||||||
|
event, err := builder.Build(eventID, now, cfg.ServerName, cfg.KeyID, cfg.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %s", builder.Type, err)
|
||||||
|
}
|
||||||
|
return &event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func authEventsFromStateNeeded(eventsNeeded gomatrixserverlib.StateNeeded,
|
||||||
|
events map[common.StateTuple]*gomatrixserverlib.Event) (authEvents []gomatrixserverlib.EventReference) {
|
||||||
|
|
||||||
|
// These events are only "needed" if they exist, so if they don't exist we can safely ignore them.
|
||||||
|
if eventsNeeded.Create {
|
||||||
|
ev := events[common.StateTuple{"m.room.create", ""}]
|
||||||
|
if ev != nil {
|
||||||
|
authEvents = append(authEvents, ev.EventReference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventsNeeded.JoinRules {
|
||||||
|
ev := events[common.StateTuple{"m.room.join_rules", ""}]
|
||||||
|
if ev != nil {
|
||||||
|
authEvents = append(authEvents, ev.EventReference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventsNeeded.PowerLevels {
|
||||||
|
ev := events[common.StateTuple{"m.room.power_levels", ""}]
|
||||||
|
if ev != nil {
|
||||||
|
authEvents = append(authEvents, ev.EventReference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range eventsNeeded.Member {
|
||||||
|
ev := events[common.StateTuple{"m.room.member", userID}]
|
||||||
|
if ev != nil {
|
||||||
|
authEvents = append(authEvents, ev.EventReference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, token := range eventsNeeded.ThirdPartyInvite {
|
||||||
|
ev := events[common.StateTuple{"m.room.member", token}]
|
||||||
|
if ev != nil {
|
||||||
|
authEvents = append(authEvents, ev.EventReference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type authEventProvider struct {
|
||||||
|
events map[common.StateTuple]*gomatrixserverlib.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) Create() (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
return a.fetch(common.StateTuple{"m.room.create", ""})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) JoinRules() (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
return a.fetch(common.StateTuple{"m.room.join_rules", ""})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) PowerLevels() (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
return a.fetch(common.StateTuple{"m.room.power_levels", ""})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) Member(stateKey string) (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
return a.fetch(common.StateTuple{"m.room.member", stateKey})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) ThirdPartyInvite(stateKey string) (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
return a.fetch(common.StateTuple{"m.room.third_party_invite", stateKey})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authEventProvider) fetch(tuple common.StateTuple) (ev *gomatrixserverlib.Event, err error) {
|
||||||
|
ev, ok := a.events[tuple]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Cannot find auth event %+v", tuple)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyString() *string {
|
||||||
|
skey := ""
|
||||||
|
return &skey
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
vendor/manifest
vendored
2
vendor/manifest
vendored
|
|
@ -92,7 +92,7 @@
|
||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "ce2ae9c5812346444b0ca75d57834794cde03fb7",
|
"revision": "4218890fdd60e73cc5539ec40b86fd51568f4a19",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin, keyID strin
|
||||||
EventBuilder
|
EventBuilder
|
||||||
EventID string `json:"event_id"`
|
EventID string `json:"event_id"`
|
||||||
RawContent rawJSON `json:"content"`
|
RawContent rawJSON `json:"content"`
|
||||||
RawUnsigned rawJSON `json:"unsigned"`
|
RawUnsigned rawJSON `json:"unsigned,omitempty"`
|
||||||
OriginServerTS int64 `json:"origin_server_ts"`
|
OriginServerTS int64 `json:"origin_server_ts"`
|
||||||
Origin string `json:"origin"`
|
Origin string `json:"origin"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue