forked from cory/tildefriends
		
	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:
		
							
								
								
									
										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(); | ||||
		Reference in New Issue
	
	Block a user