Merge branch 'main' of http://git.jerl.zone/jerl/vybe
commit
f347e659e9
|
@ -4,20 +4,30 @@ window.currentThreadId = 1;
|
||||||
|
|
||||||
function chooseThread(thread) {
|
function chooseThread(thread) {
|
||||||
if (window.currentThreadId)
|
if (window.currentThreadId)
|
||||||
document.getElementById(`thread${window.currentThreadId}`).classList.remove('selected');
|
document
|
||||||
document.getElementById(`thread${thread.id}`).classList.add('selected');
|
.getElementById(`thread${window.currentThreadId}`)
|
||||||
|
.classList.remove("active");
|
||||||
|
document.getElementById(`thread${thread.id}`).classList.add("active");
|
||||||
window.currentThreadId = thread.id;
|
window.currentThreadId = thread.id;
|
||||||
window.earliestMessage = null;
|
window.earliestMessage = null;
|
||||||
document.getElementById("messages").innerHTML = "";
|
document.getElementById("messages").innerHTML = "";
|
||||||
document.getElementById("threadname").textContent = thread.name;
|
document.getElementById("threadname").textContent = thread.name;
|
||||||
|
if (!thread.permissions.post) {
|
||||||
|
document.getElementById("msginput").classList.add("hidden");
|
||||||
|
} else {
|
||||||
|
document.getElementById("msginput").classList.remove("hidden");
|
||||||
|
}
|
||||||
loadMessages();
|
loadMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMessages() {
|
function loadMessages() {
|
||||||
window.emit("get_history", {
|
window.emit(
|
||||||
|
"get_history",
|
||||||
|
{
|
||||||
before: window.earliestMessage,
|
before: window.earliestMessage,
|
||||||
thread: window.currentThreadId,
|
thread: window.currentThreadId,
|
||||||
}, msg => {
|
},
|
||||||
|
msg => {
|
||||||
if (msg.messages.length > 0) {
|
if (msg.messages.length > 0) {
|
||||||
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
|
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
|
||||||
for (let message of msg.messages)
|
for (let message of msg.messages)
|
||||||
|
@ -31,28 +41,25 @@ function loadMessages() {
|
||||||
document.getElementById("loadmore").classList.add("hidden");
|
document.getElementById("loadmore").classList.add("hidden");
|
||||||
else
|
else
|
||||||
document.getElementById("loadmore").classList.remove("hidden");
|
document.getElementById("loadmore").classList.remove("hidden");
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addThread(thread) {
|
function addThread(thread, top) {
|
||||||
let node = html.node`
|
let node = html.node`
|
||||||
<div class='thread' onclick=${() => {
|
<div class='thread' onclick=${() => chooseThread(thread)}>${
|
||||||
chooseThread(thread);
|
thread.name
|
||||||
if (!thread.permissions.post) {
|
}</div>`;
|
||||||
document.getElementById("msginput").classList.add("hidden");
|
|
||||||
} else {
|
|
||||||
document.getElementById("msginput").classList.remove("hidden");
|
|
||||||
}
|
|
||||||
}}>${thread.name}</div>`;
|
|
||||||
node.id = `thread${thread.id}`;
|
node.id = `thread${thread.id}`;
|
||||||
document.getElementById("threadlist").appendChild(node);
|
document.getElementById("threadlist")[top ? "prepend" : "appendChild"](node);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMember() {
|
function addMember() {
|
||||||
const name = document.getElementById("membername").value;
|
const name = document.getElementById("membername").value;
|
||||||
window.threadmembers.push(name);
|
window.threadmembers.push(name);
|
||||||
document.getElementById("memberlist").appendChild(
|
document
|
||||||
html.node`<p class='member'>${name}</p>`);
|
.getElementById("memberlist")
|
||||||
|
.appendChild(html.node`<p class='member'>${name}</p>`);
|
||||||
document.getElementById("membername").value = "";
|
document.getElementById("membername").value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +72,14 @@ async function createThread(e) {
|
||||||
}
|
}
|
||||||
let members = window.threadmembers.map(name => ({ name }));
|
let members = window.threadmembers.map(name => ({ name }));
|
||||||
const perms = document.querySelector(
|
const perms = document.querySelector(
|
||||||
'input[name="permissions"]:checked').value;
|
'input[name="permissions"]:checked'
|
||||||
|
).value;
|
||||||
if (perms === "private_view")
|
if (perms === "private_view")
|
||||||
members = (await new Promise(resolve =>
|
members = (
|
||||||
|
await new Promise(resolve =>
|
||||||
window.emit("get_keys", { names: window.threadmembers }, resolve)
|
window.emit("get_keys", { names: window.threadmembers }, resolve)
|
||||||
)).keys;
|
)
|
||||||
|
).keys;
|
||||||
let permissions;
|
let permissions;
|
||||||
if (perms === "public") {
|
if (perms === "public") {
|
||||||
permissions = {
|
permissions = {
|
||||||
|
@ -101,19 +111,24 @@ async function createThread(e) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
window.emit("create_thread", {
|
window.emit(
|
||||||
|
"create_thread",
|
||||||
|
{
|
||||||
name: name.value,
|
name: name.value,
|
||||||
permissions,
|
permissions,
|
||||||
members
|
members,
|
||||||
}, msg => {
|
},
|
||||||
|
msg => {
|
||||||
chooseThread({
|
chooseThread({
|
||||||
name: name.value,
|
name: name.value,
|
||||||
id: msg.id
|
id: msg.id,
|
||||||
});
|
});
|
||||||
document.getElementById('createthread').remove();
|
// since the form exists, this will perform cleanup
|
||||||
|
newThread();
|
||||||
document.getElementById("loadmore").classList.add("hidden");
|
document.getElementById("loadmore").classList.add("hidden");
|
||||||
document.getElementById("msginput").classList.remove("hidden");
|
document.getElementById("msginput").classList.remove("hidden");
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMessage(e) {
|
function sendMessage(e) {
|
||||||
|
@ -128,18 +143,17 @@ function sendMessage(e) {
|
||||||
document.getElementById("msg").value = "";
|
document.getElementById("msg").value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function newThread(e) {
|
function newThread() {
|
||||||
let form = document.getElementById('createthread');
|
let form = document.getElementById('createthread');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.remove();
|
form.remove();
|
||||||
e.target.textContent = 'create';
|
document.getElementById("newthread").textContent = 'create';
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
window.threadmembers = [window.name];
|
window.threadmembers = [window.name];
|
||||||
document.getElementById('threads').insertAdjacentElement('afterend', html.node`
|
document.getElementById('threads').insertAdjacentElement('afterend', html.node`
|
||||||
<form id="createthread" class='column' onsubmit=${createThread}>
|
<form id="createthread" class='column' onsubmit=${createThread}>
|
||||||
<h3>create thread</h3>
|
<h3>create thread</h3>
|
||||||
<label for="newthreadname">thread name</label>
|
<label for="newthreadname" class="heading">thread name</label>
|
||||||
<input type="text" id="newthreadname" />
|
<input type="text" id="newthreadname" />
|
||||||
<p id='permissions'>thread permissions</p>
|
<p id='permissions'>thread permissions</p>
|
||||||
<input type="radio" id="public" name="permissions" value="public" checked />
|
<input type="radio" id="public" name="permissions" value="public" checked />
|
||||||
|
@ -153,7 +167,7 @@ function newThread(e) {
|
||||||
/>
|
/>
|
||||||
<label for="private_view">only members can view and post</label
|
<label for="private_view">only members can view and post</label
|
||||||
><br /><br />
|
><br /><br />
|
||||||
<span>members</span><br />
|
<label class="heading" for="membername">members</label>
|
||||||
<input type="text" id="membername" placeholder="username" onkeydown=${(e) => {
|
<input type="text" id="membername" placeholder="username" onkeydown=${(e) => {
|
||||||
if (e.key == "Enter") {
|
if (e.key == "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -165,10 +179,11 @@ function newThread(e) {
|
||||||
<p class='member'>${window.name}</p>
|
<p class='member'>${window.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<input id="submitthread" type="submit" value="create" />
|
<button id="submitthread" type="submit">create</button>
|
||||||
</form>
|
</form>
|
||||||
`);
|
`
|
||||||
e.target.textContent = 'cancel';
|
);
|
||||||
|
document.getElementById("newthread").textContent = 'cancel';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +193,8 @@ function switchTab(event){
|
||||||
for (let tab of document.querySelectorAll('.tabcontent'))
|
for (let tab of document.querySelectorAll('.tabcontent'))
|
||||||
tab.classList.add('hidden');
|
tab.classList.add('hidden');
|
||||||
event.target.classList.add('active');
|
event.target.classList.add('active');
|
||||||
document.getElementById(event.target.id.substring(0, event.target.id.length - 3))
|
document
|
||||||
|
.getElementById(event.target.id.substring(0, event.target.id.length - 3))
|
||||||
.classList.remove('hidden');
|
.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,10 +210,14 @@ render(document.body, html`
|
||||||
thread: <strong id="threadname">meow</strong>
|
thread: <strong id="threadname">meow</strong>
|
||||||
</div>
|
</div>
|
||||||
<div id='tabs'>
|
<div id='tabs'>
|
||||||
<button id='messagetab' class='tab active' onclick=${switchTab}>messages</button><button id='spacetab' class='tab' onclick=${switchTab}>space</button>
|
<button id='messagetab' class='tab active' onclick=${switchTab}>
|
||||||
|
messages
|
||||||
|
</button><button id='spacetab' class='tab' onclick=${switchTab}>space</button>
|
||||||
</div>
|
</div>
|
||||||
<div id='message' class='tabcontent'>
|
<div id='message' class='tabcontent'>
|
||||||
<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button>
|
<button id="loadmore" class="hidden" onclick=${loadMessages}>
|
||||||
|
load more messages
|
||||||
|
</button>
|
||||||
<div id='messages'></div>
|
<div id='messages'></div>
|
||||||
<form id="msginput" onsubmit=${sendMessage}>
|
<form id="msginput" onsubmit=${sendMessage}>
|
||||||
<input type="text" placeholder="write a message..." id="msg" />
|
<input type="text" placeholder="write a message..." id="msg" />
|
||||||
|
@ -219,7 +239,7 @@ window.socket.on("new_message", (msg) => {
|
||||||
if (!window.earliestMessage)
|
if (!window.earliestMessage)
|
||||||
window.earliestMessage = msg.id;
|
window.earliestMessage = msg.id;
|
||||||
});
|
});
|
||||||
window.socket.on("new_thread", addThread);
|
window.socket.on("new_thread", thread => addThread(thread, true));
|
||||||
|
|
||||||
window.emit("list_threads", {}, msg => {
|
window.emit("list_threads", {}, msg => {
|
||||||
document.getElementById("threadlist").innerHTML = "";
|
document.getElementById("threadlist").innerHTML = "";
|
||||||
|
|
|
@ -16,18 +16,25 @@ async function auth() {
|
||||||
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
|
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
|
||||||
signingKeys: window.keys.priv,
|
signingKeys: window.keys.priv,
|
||||||
});
|
});
|
||||||
window.socket.emit("authenticate", { name: window.name, message: sig },
|
window.socket.emit(
|
||||||
|
"authenticate",
|
||||||
|
{ name: window.name, message: sig },
|
||||||
msg => {
|
msg => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
console.log('authenticate failed');
|
console.log('authenticate failed', msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (document.getElementById("register")) {
|
||||||
document.getElementById("register").remove();
|
document.getElementById("register").remove();
|
||||||
import('/app.js');
|
import('/app.js');
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(document.body, html`
|
render(
|
||||||
|
document.body,
|
||||||
|
html`
|
||||||
<div id="register" class='hidden'>
|
<div id="register" class='hidden'>
|
||||||
<h1>welcome to vybe</h1>
|
<h1>welcome to vybe</h1>
|
||||||
<h3>a communication network (beta)</h3>
|
<h3>a communication network (beta)</h3>
|
||||||
|
@ -36,7 +43,8 @@ render(document.body, html`
|
||||||
for security, rather than passwords. your keys are stored in your
|
for security, rather than passwords. your keys are stored in your
|
||||||
browser storage only, so do this on a browser you can access again.
|
browser storage only, so do this on a browser you can access again.
|
||||||
</p>
|
</p>
|
||||||
<form onsubmit=${async e => {
|
<form
|
||||||
|
onsubmit=${async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const name = document.getElementById("name").value;
|
const name = document.getElementById("name").value;
|
||||||
if (!name)
|
if (!name)
|
||||||
|
@ -46,10 +54,16 @@ render(document.body, html`
|
||||||
});
|
});
|
||||||
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
|
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
|
||||||
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
|
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
|
||||||
window.emit("create_user", { name, pubkey: keys.publicKey }, msg => {
|
window.emit(
|
||||||
|
"create_user",
|
||||||
|
{ name, pubkey: keys.publicKey },
|
||||||
|
(msg) => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
document.querySelector('#registerform').insertAdjacentHTML('afterend', `
|
document.querySelector('#registerform').insertAdjacentHTML(
|
||||||
<p>${msg.message}</p>`);
|
'afterend',
|
||||||
|
`
|
||||||
|
<p>${msg.message}</p>`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.keys = { priv, pub };
|
window.keys = { priv, pub };
|
||||||
|
@ -57,23 +71,37 @@ render(document.body, html`
|
||||||
localStorage.setItem("name", name);
|
localStorage.setItem("name", name);
|
||||||
window.name = name;
|
window.name = name;
|
||||||
auth();
|
auth();
|
||||||
});
|
}
|
||||||
}} id="registerform">
|
);
|
||||||
|
}}
|
||||||
|
id="registerform"
|
||||||
|
>
|
||||||
<label for="name">username: </label>
|
<label for="name">username: </label>
|
||||||
<input id="name" type="text" />
|
<input id="name" type="text" />
|
||||||
<input id="submit" type="submit" value='generate keys & register'>
|
<input id="submit" type="submit" value='generate keys & register' />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const gensession = async () => {
|
||||||
|
window.session = rand();
|
||||||
|
|
||||||
|
window.emit = (type, data, callback) =>
|
||||||
|
window.socket.emit(
|
||||||
|
type,
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
__session: window.session,
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
window.socket = io();
|
window.socket = io();
|
||||||
|
|
||||||
window.session = rand();
|
await gensession();
|
||||||
window.emit = (type, data, callback) => window.socket.emit(type, {
|
|
||||||
...data,
|
|
||||||
__session: window.session,
|
|
||||||
}, callback);
|
|
||||||
|
|
||||||
let keys = localStorage.getItem("keys");
|
let keys = localStorage.getItem("keys");
|
||||||
if (keys) {
|
if (keys) {
|
||||||
|
@ -87,4 +115,10 @@ window.onload = async () => {
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
document.getElementById('register').classList.remove('hidden');
|
document.getElementById('register').classList.remove('hidden');
|
||||||
|
|
||||||
|
window.socket.io.on("reconnect", async attempt => {
|
||||||
|
await gensession();
|
||||||
|
if (localStorage.getItem("keys"))
|
||||||
|
await auth();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,23 +18,60 @@
|
||||||
body,
|
body,
|
||||||
button,
|
button,
|
||||||
input {
|
input {
|
||||||
background: #020202;
|
|
||||||
color: #eaeaea;
|
color: #eaeaea;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
|
height: 100%;
|
||||||
|
background: #020202;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-width: min-content;
|
min-width: min-content;
|
||||||
}
|
}
|
||||||
h3, h4 {
|
button,
|
||||||
margin: 10px 0;
|
input,
|
||||||
|
.tab {
|
||||||
|
padding: 5px 7px;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
border-color: #767676;
|
background: #303030;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
background: #171717;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
border-bottom: 2px solid #4f4f4f;
|
||||||
|
}
|
||||||
|
input::placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
.thread:hover,
|
||||||
|
.tab:hover {
|
||||||
|
background-color: #3b3b3b;
|
||||||
|
}
|
||||||
|
.tab.active,
|
||||||
|
.thread.active,
|
||||||
|
button:hover {
|
||||||
|
background-color: #4f4f4f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
label.heading {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.column {
|
.column {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -44,11 +81,10 @@
|
||||||
#threads {
|
#threads {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
}
|
}
|
||||||
.thread.selected {
|
.thread {
|
||||||
background-color: #4f4f4f;
|
display: block;
|
||||||
}
|
width: 100%;
|
||||||
.thread:hover {
|
text-align: left;
|
||||||
background-color: #3b3b3b;
|
|
||||||
}
|
}
|
||||||
#newthread {
|
#newthread {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
@ -59,28 +95,46 @@
|
||||||
#permissions {
|
#permissions {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
#thread {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
#title {
|
#title {
|
||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
}
|
}
|
||||||
|
#tabs {
|
||||||
|
margin-block: 2px;
|
||||||
|
}
|
||||||
.tab {
|
.tab {
|
||||||
|
padding: 5px 7px;
|
||||||
|
background-color: #1f1f1f;
|
||||||
border: 0;
|
border: 0;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding: 5px 7px;
|
color: #ddd;
|
||||||
color: #ccc;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.tab.active {
|
.tabcontent {
|
||||||
background-color: #4f4f4f;
|
flex-grow: 1;
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
.tab:hover {
|
#message {
|
||||||
background-color:#3b3b3b;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
#messages {
|
#messages {
|
||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
#msginput {
|
#msginput {
|
||||||
margin-top: 15px;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
#msg {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#sendmsg {
|
||||||
|
margin: 2px 3px;
|
||||||
}
|
}
|
||||||
.message {
|
.message {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
@ -97,6 +151,5 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body></body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
6
index.js
6
index.js
|
@ -3,7 +3,8 @@ const http = require("http");
|
||||||
const { Server } = require("socket.io");
|
const { Server } = require("socket.io");
|
||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
|
|
||||||
const events = Object.fromEntries([
|
const events = Object.fromEntries(
|
||||||
|
[
|
||||||
'create_user',
|
'create_user',
|
||||||
'get_history',
|
'get_history',
|
||||||
'send_message',
|
'send_message',
|
||||||
|
@ -11,7 +12,8 @@ const events = Object.fromEntries([
|
||||||
'create_thread',
|
'create_thread',
|
||||||
'list_threads',
|
'list_threads',
|
||||||
'get_keys',
|
'get_keys',
|
||||||
].map(event => [event, require('./src/' + event)]));
|
].map(event => [event, require('./src/' + event)])
|
||||||
|
);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
|
|
|
@ -85,7 +85,7 @@ const create_thread = async (msg, respond, socket, io) => {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (let member of msg.members) {
|
for (let member of msg.members) {
|
||||||
for (let socket of io.cache[member.user]) {
|
for (let socket of io.cache[member.name]) {
|
||||||
io.to(socket).emit("new_thread", {
|
io.to(socket).emit("new_thread", {
|
||||||
name: msg.name,
|
name: msg.name,
|
||||||
id: insert.rows[0].id,
|
id: insert.rows[0].id,
|
||||||
|
|
Loading…
Reference in New Issue