Exposed deleting users, mostly for my own testing, and used it to make a primitive admin app. Add a handful of apps I've been kicking around without version control, while I'm at it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3950 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
353f2ccc13
commit
fbfbd6a6b4
1
apps/cory/admin.json
Normal file
1
apps/cory/admin.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"tildefriends-app","files":{"app.js":"&xotWQ8M3xgnWAPM/1TdrLmkcCyxGPiXqg9CsBm2ngcc=.sha256","index.html":"&PrdNng+/SYCFSEbx+E7tMKxs4/ypPDxbRlak4tGN/SM=.sha256","lit.min.js":"&3FfrVflmGr0n4lvN0GriN1Qz1lEw31SbZxRSJrcXR28=.sha256","script.js":"&hW7AyNMgC+paQBFDcggxmhwNWmEY+5HofubRalcz6u8=.sha256"}}
|
13
apps/cory/admin/app.js
Normal file
13
apps/cory/admin/app.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as tfrpc from '/tfrpc.js';
|
||||||
|
|
||||||
|
tfrpc.register(function delete_user(user) {
|
||||||
|
return core.deleteUser(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let data = {
|
||||||
|
users: await core.users(),
|
||||||
|
};
|
||||||
|
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
|
||||||
|
}
|
||||||
|
main();
|
10
apps/cory/admin/index.html
Normal file
10
apps/cory/admin/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>const g_data = $data;</script>
|
||||||
|
</head>
|
||||||
|
<body style="color: #fff">
|
||||||
|
<h1>Test</h1>
|
||||||
|
</body>
|
||||||
|
<script type="module" src="script.js"></script>
|
||||||
|
</html>
|
13
apps/cory/admin/lit.min.js
vendored
Normal file
13
apps/cory/admin/lit.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
apps/cory/admin/script.js
Normal file
21
apps/cory/admin/script.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {html, render} from './lit.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
|
||||||
|
function delete_user(user) {
|
||||||
|
if (confirm(`Are you sure you want to delete the user "${user}"?`)) {
|
||||||
|
tfrpc.rpc.delete_user(user).then(function() {
|
||||||
|
alert(`User "${user}" deleted successfully.`);
|
||||||
|
}).catch(function(error) {
|
||||||
|
alert(`Failed to delete user "${user}": ${JSON.stringify(error, null, 2)}.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
const user_template = (user) => html`<li><button @click=${(e) => delete_user(user)}>Delete</button> ${user}</li>`;
|
||||||
|
const users_template = (users) =>
|
||||||
|
html`<ul>
|
||||||
|
${users.map(u => user_template(u))}
|
||||||
|
</ul>`;
|
||||||
|
render(users_template(g_data.users), document.body);
|
||||||
|
});
|
1
apps/cory/api.json
Normal file
1
apps/cory/api.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"tildefriends-app","files":{"app.js":"&p35JmopfHf8hFh3Y9x6LrIxiUwaJZ5Nabzi2sVXpKoo=.sha256"}}
|
11
apps/cory/api/app.js
Normal file
11
apps/cory/api/app.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
var global = Function('return this')();
|
||||||
|
function treeify(o) {
|
||||||
|
if (typeof(o) == 'object') {
|
||||||
|
return Object.fromEntries(Object.keys(o).map(x => [x, treeify(o[x])]));
|
||||||
|
} else if (typeof(o) == 'function') {
|
||||||
|
return 'function';
|
||||||
|
} else if (typeof(o) == 'string' || typeof(o) == 'number') {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.setDocument(`<pre style="color:#fff">${JSON.stringify(treeify(global), null, 2)}</pre>`);
|
1
apps/cory/db.json
Normal file
1
apps/cory/db.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"tildefriends-app","files":{"app.js":"&V5o5IM9/OUyIsVkjkMW/X0i/tflQOSVJuJBmHdMT9aM=.sha256"}}
|
70
apps/cory/db/app.js
Normal file
70
apps/cory/db/app.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
async function database_list() {
|
||||||
|
var dbs = await databases();
|
||||||
|
var doc = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="background: #888">
|
||||||
|
<h1>Databases</h1>
|
||||||
|
<ul id="dbs"></ul>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
function populate_dbs(id, dbs) {
|
||||||
|
var list = document.getElementById(id);
|
||||||
|
for (let db of dbs) {
|
||||||
|
var li = list.appendChild(document.createElement('li'));
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.innerText = db;
|
||||||
|
a.href = './#' + db;
|
||||||
|
a.target = '_top';
|
||||||
|
li.appendChild(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populate_dbs('dbs', ${JSON.stringify(dbs)});
|
||||||
|
</script>
|
||||||
|
</html>`
|
||||||
|
app.setDocument(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function key_list(db) {
|
||||||
|
let keys = await db.getAll();
|
||||||
|
let object = {};
|
||||||
|
for (let key of keys) {
|
||||||
|
object[key] = await db.get(key);
|
||||||
|
}
|
||||||
|
let doc = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="background: #888">
|
||||||
|
<a href="#" target="_top">back</a>
|
||||||
|
<h1>Keys</h1>
|
||||||
|
<ul id="keys"></ul>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
function populate_dbs(id, keys) {
|
||||||
|
var list = document.getElementById(id);
|
||||||
|
for (let [key, value] of Object.entries(keys)) {
|
||||||
|
var li = list.appendChild(document.createElement('li'));
|
||||||
|
li.innerText = key + ' = ' + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populate_dbs('keys', ${JSON.stringify(object)});
|
||||||
|
</script>
|
||||||
|
</html>`
|
||||||
|
app.setDocument(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.register('message', async function(message) {
|
||||||
|
if (message.event == 'hashChange') {
|
||||||
|
let hash = message.hash.substring(1);
|
||||||
|
if (hash.startsWith(':shared:')) {
|
||||||
|
let parts = hash.split(':');
|
||||||
|
let packageName = parts[3];
|
||||||
|
let key = parts.slice(4).join(':');
|
||||||
|
key_list(await my_shared_database(packageName, key));
|
||||||
|
} else if (hash.length) {
|
||||||
|
key_list(await database(hash.split(':').slice(1).join(':')));
|
||||||
|
} else {
|
||||||
|
database_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
database_list();
|
1
apps/cory/follow.json
Normal file
1
apps/cory/follow.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"tildefriends-app","files":{"app.js":"&+LbIl429+UZeS9Nh8zO6n7pzRfWOfFF2K/Hg7Kq2HQo=.sha256"}}
|
159
apps/cory/follow/app.js
Normal file
159
apps/cory/follow/app.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var g_following_cache = {};
|
||||||
|
var g_following_deep_cache = {};
|
||||||
|
var g_about_cache = {};
|
||||||
|
|
||||||
|
async function following(db, id) {
|
||||||
|
if (g_following_cache[id]) {
|
||||||
|
return g_following_cache[id];
|
||||||
|
}
|
||||||
|
var o = await db.get(id + ":following");
|
||||||
|
const k_version = 5;
|
||||||
|
var f = o ? JSON.parse(o) : o;
|
||||||
|
if (!f || f.version != k_version) {
|
||||||
|
f = {users: [], sequence: 0, version: k_version};
|
||||||
|
}
|
||||||
|
f.users = new Set(f.users);
|
||||||
|
await ssb.sqlStream(
|
||||||
|
"SELECT "+
|
||||||
|
" sequence, "+
|
||||||
|
" json_extract(content, '$.contact') AS contact, "+
|
||||||
|
" json_extract(content, '$.following') AS following "+
|
||||||
|
"FROM messages "+
|
||||||
|
"WHERE "+
|
||||||
|
" author = ?1 AND "+
|
||||||
|
" sequence > ?2 AND "+
|
||||||
|
" json_extract(content, '$.type') = 'contact' "+
|
||||||
|
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
|
||||||
|
"ORDER BY sequence",
|
||||||
|
[id, f.sequence],
|
||||||
|
function(row) {
|
||||||
|
if (row.following) {
|
||||||
|
f.users.add(row.contact);
|
||||||
|
} else {
|
||||||
|
f.users.delete(row.contact);
|
||||||
|
}
|
||||||
|
f.sequence = row.sequence;
|
||||||
|
});
|
||||||
|
var as_set = f.users;
|
||||||
|
f.users = Array.from(f.users).sort();
|
||||||
|
var j = JSON.stringify(f);
|
||||||
|
if (o != j) {
|
||||||
|
await db.set(id + ":following", j);
|
||||||
|
}
|
||||||
|
f.users = as_set;
|
||||||
|
g_following_cache[id] = f.users;
|
||||||
|
return f.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function followingDeep(db, seed_ids, depth) {
|
||||||
|
if (depth <= 0) {
|
||||||
|
return seed_ids;
|
||||||
|
}
|
||||||
|
var key = JSON.stringify([seed_ids, depth]);
|
||||||
|
if (g_following_deep_cache[key]) {
|
||||||
|
return g_following_deep_cache[key];
|
||||||
|
}
|
||||||
|
var f = await Promise.all(seed_ids.map(x => following(db, x).then(x => [...x])));
|
||||||
|
var ids = [].concat(...f);
|
||||||
|
var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1);
|
||||||
|
x = [...new Set([].concat(...x, ...seed_ids))].sort();
|
||||||
|
g_following_deep_cache[key] = x;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAbout(db, id) {
|
||||||
|
if (g_about_cache[id]) {
|
||||||
|
return g_about_cache[id];
|
||||||
|
}
|
||||||
|
var o = await db.get(id + ":about");
|
||||||
|
const k_version = 4;
|
||||||
|
var f = o ? JSON.parse(o) : o;
|
||||||
|
if (!f || f.version != k_version) {
|
||||||
|
f = {about: {}, sequence: 0, version: k_version};
|
||||||
|
}
|
||||||
|
await ssb.sqlStream(
|
||||||
|
"SELECT "+
|
||||||
|
" sequence, "+
|
||||||
|
" content "+
|
||||||
|
"FROM messages "+
|
||||||
|
"WHERE "+
|
||||||
|
" author = ?1 AND "+
|
||||||
|
" sequence > ?2 AND "+
|
||||||
|
" json_extract(content, '$.type') = 'about' AND "+
|
||||||
|
" json_extract(content, '$.about') = ?1 "+
|
||||||
|
"UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 "+
|
||||||
|
"ORDER BY sequence",
|
||||||
|
[id, f.sequence],
|
||||||
|
function(row) {
|
||||||
|
f.sequence = row.sequence;
|
||||||
|
if (row.content) {
|
||||||
|
var about = {};
|
||||||
|
try {
|
||||||
|
about = JSON.parse(row.content);
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
delete about.about;
|
||||||
|
delete about.type;
|
||||||
|
f.about = Object.assign(f.about, about);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var j = JSON.stringify(f);
|
||||||
|
if (o != j) {
|
||||||
|
await db.set(id + ":about", j);
|
||||||
|
}
|
||||||
|
g_about_cache[id] = f.about;
|
||||||
|
return f.about;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSize(db, id) {
|
||||||
|
let size = 0;
|
||||||
|
await ssb.sqlStream(
|
||||||
|
"SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
|
||||||
|
[id],
|
||||||
|
function (row) {
|
||||||
|
size += row.size;
|
||||||
|
});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function niceSize(bytes) {
|
||||||
|
let value = bytes;
|
||||||
|
let unit = 'B';
|
||||||
|
const k_units = ['kB', 'MB', 'GB', 'TB'];
|
||||||
|
for (let u of k_units) {
|
||||||
|
if (value >= 1024) {
|
||||||
|
value /= 1024;
|
||||||
|
unit = u;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.round(value * 10) / 10 + ' ' + unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildTree(db, root, indent, depth) {
|
||||||
|
var f = await following(db, root);
|
||||||
|
var result = indent + '[' + f.size + '] ' + '<a target="_top" href="../index/#' + root + '">' + ((await getAbout(db, root)).name || root) + '</a> ' + niceSize(await getSize(db, root)) + '\n';
|
||||||
|
if (depth > 0) {
|
||||||
|
for (let next of f) {
|
||||||
|
result += await buildTree(db, next, indent + ' ', depth - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await app.setDocument('<pre style="color: #fff">building...</pre>');
|
||||||
|
var db = await database('ssb');
|
||||||
|
var whoami = await ssb.getIdentities();
|
||||||
|
var tree = '';
|
||||||
|
for (let id of whoami) {
|
||||||
|
await app.setDocument(`<pre style="color: #fff">building... ${id}</pre>`);
|
||||||
|
tree += await buildTree(db, id, '', 2);
|
||||||
|
}
|
||||||
|
await app.setDocument('<pre style="color: #fff">FOLLOWING:\n' + tree + '</pre>');
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
21
core/core.js
21
core/core.js
@ -224,6 +224,27 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (process.credentials?.permissions?.administration) {
|
||||||
|
imports.core.deleteUser = function(user) {
|
||||||
|
return imports.core.permissionTest('delete_user').then(function() {
|
||||||
|
let db = new Database('auth');
|
||||||
|
|
||||||
|
db.remove('user:' + user);
|
||||||
|
|
||||||
|
let users = new Set();
|
||||||
|
let users_original = db.get('users');
|
||||||
|
try {
|
||||||
|
users = new Set(JSON.parse(users_original));
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
users.delete(user);
|
||||||
|
users = JSON.stringify([...users].sort());
|
||||||
|
if (users !== users_original) {
|
||||||
|
db.set('users', users);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
if (options.api) {
|
if (options.api) {
|
||||||
imports.app = {};
|
imports.app = {};
|
||||||
for (let i in options.api) {
|
for (let i in options.api) {
|
||||||
|
@ -287,8 +287,12 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
const char* k_export[] = {
|
const char* k_export[] = {
|
||||||
|
"/~cory/api",
|
||||||
|
"/~cory/admin",
|
||||||
"/~cory/apps",
|
"/~cory/apps",
|
||||||
|
"/~cory/db",
|
||||||
"/~cory/docs",
|
"/~cory/docs",
|
||||||
|
"/~cory/follow",
|
||||||
"/~cory/ssb",
|
"/~cory/ssb",
|
||||||
};
|
};
|
||||||
for (int i = 0; i < (int)_countof(k_export); i++)
|
for (int i = 0; i < (int)_countof(k_export); i++)
|
||||||
|
Loading…
Reference in New Issue
Block a user