forked from Telodendria/Telodendria
[MOD/WIP] Propagate nick/displayname changes
Some checks are pending
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run
Some checks are pending
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run
I should consider try to shove the previous state in the unsigned if possible.
This commit is contained in:
parent
4918932a0e
commit
860dc9637a
11 changed files with 108 additions and 159 deletions
21
src/Config.c
21
src/Config.c
|
@ -230,3 +230,24 @@ ConfigLogLevelToSyslog(ConfigLogLevel level)
|
||||||
}
|
}
|
||||||
return LOG_INFO;
|
return LOG_INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
ConfigGetServerName(Db * db)
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
ConfigLock(db, &config);
|
||||||
|
if (!config.ok)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = StrDuplicate(config.serverName);
|
||||||
|
|
||||||
|
ConfigUnlock(&config);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
22
src/Room.c
22
src/Room.c
|
@ -43,6 +43,7 @@
|
||||||
#include <Schema/PduV3.h>
|
#include <Schema/PduV3.h>
|
||||||
|
|
||||||
#include <CanonicalJson.h>
|
#include <CanonicalJson.h>
|
||||||
|
#include <Config.h>
|
||||||
#include <Parser.h>
|
#include <Parser.h>
|
||||||
#include <State.h>
|
#include <State.h>
|
||||||
#include <Event.h>
|
#include <Event.h>
|
||||||
|
@ -2215,25 +2216,6 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
bool
|
bool
|
||||||
RoomJoin(Room *room, User *user)
|
RoomJoin(Room *room, User *user)
|
||||||
{
|
{
|
||||||
|
@ -2250,7 +2232,7 @@ RoomJoin(Room *room, User *user)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server = GetServerName(room->db);
|
server = ConfigGetServerName(room->db);
|
||||||
if (!server)
|
if (!server)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -30,32 +30,14 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
#include <Room.h>
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteFetchEvent, path, argp)
|
ROUTE_IMPL(RouteFetchEvent, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -90,7 +72,7 @@ ROUTE_IMPL(RouteFetchEvent, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -30,31 +30,13 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteFilter, path, argp)
|
ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -80,7 +62,7 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -30,32 +30,14 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
#include <Room.h>
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteJoinRoom, path, argp)
|
ROUTE_IMPL(RouteJoinRoom, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -97,7 +79,7 @@ ROUTE_IMPL(RouteJoinRoom, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -30,32 +30,14 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
#include <Room.h>
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteJoinRoomAlias, path, argp)
|
ROUTE_IMPL(RouteJoinRoomAlias, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -112,7 +94,7 @@ ROUTE_IMPL(RouteJoinRoomAlias, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Room.h>
|
#include <Room.h>
|
||||||
|
|
||||||
|
@ -38,26 +39,6 @@
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteMessages, path, argp)
|
ROUTE_IMPL(RouteMessages, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -102,7 +83,7 @@ ROUTE_IMPL(RouteMessages, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -30,32 +30,14 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
|
||||||
#include <Room.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
#include <Room.h>
|
||||||
|
|
||||||
#include <Schema/Filter.h>
|
#include <Schema/Filter.h>
|
||||||
|
|
||||||
static char *
|
|
||||||
GetServerName(Db * db)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
ConfigLock(db, &config);
|
|
||||||
if (!config.ok)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = StrDuplicate(config.serverName);
|
|
||||||
|
|
||||||
ConfigUnlock(&config);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteSendEvent, path, argp)
|
ROUTE_IMPL(RouteSendEvent, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -93,7 +75,7 @@ ROUTE_IMPL(RouteSendEvent, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
@ -209,7 +191,7 @@ ROUTE_IMPL(RouteSendState, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName = GetServerName(db);
|
serverName = ConfigGetServerName(db);
|
||||||
if (!serverName)
|
if (!serverName)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -28,10 +28,52 @@
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <User.h>
|
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <User.h>
|
||||||
|
#include <Room.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
SendMembership(Db *db, User *user)
|
||||||
|
{
|
||||||
|
char *displayname = UserGetProfile(user, "displayname");
|
||||||
|
char *avatar_url = UserGetProfile(user, "avatar_url");
|
||||||
|
Array *joins = UserListJoins(user);
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
char *server_name = ConfigGetServerName(db);
|
||||||
|
|
||||||
|
CommonID *user_id = UserIdParse(UserGetName(user), server_name);
|
||||||
|
char *sender = ParserRecomposeCommonID(*user_id);
|
||||||
|
Free(server_name);
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(joins); i++)
|
||||||
|
{
|
||||||
|
char *room_id = ArrayGet(joins, i);
|
||||||
|
Room *room = RoomLock(db, room_id);
|
||||||
|
HashMap *content = HashMapCreate();
|
||||||
|
HashMap *membership = RoomEventCreate(
|
||||||
|
sender, "m.room.member", sender,
|
||||||
|
content
|
||||||
|
);
|
||||||
|
|
||||||
|
HashMapSet(content, "membership", JsonValueString("join"));
|
||||||
|
HashMapSet(content, "displayname", JsonValueString(displayname));
|
||||||
|
HashMapSet(content, "avatar_url", JsonValueString(avatar_url));
|
||||||
|
|
||||||
|
JsonFree(RoomEventSend(room, membership));
|
||||||
|
JsonFree(membership);
|
||||||
|
|
||||||
|
RoomUnlock(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserFreeList(joins);
|
||||||
|
UserIdFree(user_id);
|
||||||
|
Free(sender);
|
||||||
|
}
|
||||||
|
|
||||||
ROUTE_IMPL(RouteUserProfile, path, argp)
|
ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
|
@ -142,6 +184,8 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: This may change in the future. */
|
||||||
entry = ArrayGet(path, 1);
|
entry = ArrayGet(path, 1);
|
||||||
if (StrEquals(entry, "displayname") ||
|
if (StrEquals(entry, "displayname") ||
|
||||||
StrEquals(entry, "avatar_url"))
|
StrEquals(entry, "avatar_url"))
|
||||||
|
@ -153,6 +197,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
/* TODO: Make UserSetProfile notify other parties of
|
/* TODO: Make UserSetProfile notify other parties of
|
||||||
* the change */
|
* the change */
|
||||||
UserSetProfile(user, entry, value);
|
UserSetProfile(user, entry, value);
|
||||||
|
SendMembership(db, user);
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ UserValidate(char *localpart, char *domain)
|
||||||
bool
|
bool
|
||||||
UserHistoricalValidate(char *localpart, char *domain)
|
UserHistoricalValidate(char *localpart, char *domain)
|
||||||
{
|
{
|
||||||
size_t maxLen = 255 - strlen(domain) - 1;
|
size_t maxLen = 255 - (domain ? strlen(domain) : 0) - 1;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
while (localpart[i])
|
while (localpart[i])
|
||||||
|
@ -949,7 +949,7 @@ UserIdParse(char *id, char *defaultServer)
|
||||||
userId->local = StrDuplicate(id);
|
userId->local = StrDuplicate(id);
|
||||||
ParseServerPart(defaultServer, &userId->server);
|
ParseServerPart(defaultServer, &userId->server);
|
||||||
}
|
}
|
||||||
|
userId->sigil = '@';
|
||||||
server = ParserRecomposeServerPart(userId->server);
|
server = ParserRecomposeServerPart(userId->server);
|
||||||
if (!UserHistoricalValidate(userId->local, server))
|
if (!UserHistoricalValidate(userId->local, server))
|
||||||
{
|
{
|
||||||
|
|
|
@ -98,6 +98,16 @@ extern void ConfigLock(Db *, Config *);
|
||||||
*/
|
*/
|
||||||
extern int ConfigUnlock(Config *);
|
extern int ConfigUnlock(Config *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configuration's server name from a database.
|
||||||
|
* This is an utility function that masks behind
|
||||||
|
* .Fn ConfigLock ,
|
||||||
|
* and the value returned lives on the heap, and must be freed
|
||||||
|
* with
|
||||||
|
* .Fn Free .
|
||||||
|
*/
|
||||||
|
extern char * ConfigGetServerName(Db *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a ConfigLogLevel into a valid syslog level.
|
* Converts a ConfigLogLevel into a valid syslog level.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue