parsav  users.t at [26937ca853]

File render/conf/users.t artifact 8a313a02c7 part of check-in 26937ca853


-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)

local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local terra 
regalia(acc: &lib.str.acc, rank: uint16)
	switch rank do -- TODO customizability
		case [uint16](1) then acc:lpush('πŸ‘‘') end
		case [uint16](2) then acc:lpush('πŸ”±') end
		case [uint16](3) then acc:lpush('⚜️') end
		case [uint16](4) then acc:lpush('πŸ—‘') end
		case [uint16](5) then acc:lpush('πŸ—') end
		else acc:lpush('πŸ•΄')
	end
end

local splitwords = macro(function(str)
	local words = {}
	for w in str:asvalue():gmatch('(%g+)') do words[#words + 1] = w end
	return `arrayof(pstr, [words])
end)

local rnd = lib.crypt.random
local terra 
suggest_handle(a: &lib.str.acc)
	var start = a.sz
	var puncts = array('.','_','-')
	var xXx = rnd(uint8, 0, 30) == 0
	var leet = rnd(uint8, 0, 8) == 0
	var caps = rnd(uint8, 0, 10)
	var punct: rawstring = nil
	var useadj = rnd(uint8, 0, 4) == 0
	if rnd(uint8, 0, 4) == 0 then
		punct = puncts[rnd(intptr,0,[puncts.type.N])]
	end

	var nouns = splitwords [[
		thunder bride blaze doom squad gun lord blaster
		fuck hell hound piss shit killa terror horror
		fear slaughter murder general commander commissar
		terrorist infinity slut cunt whore bitch bastard
		cock prince princess pimp gay cop slayer vampire
		vampyre blood pain brute wolf sword star sun moon
		killer murderer thief arson fire ice frost hack
		hacker god master mistress slave rage freeze flayer
		pirate ninja shadow fog mist misery glory bear
		king queen empress emperor majesty space martian
		winter fall monk katana 420 warrior banana demon
		devil ghost wraith cuck legend hero heroine goblin
		gremlin troll dragon evil overlord radiance slop
		operator rage hog bog roach wizard steel madness
		reign
	]]
	var adjs = splitwords [[
		dark super supreme ultra ultimate total infinite
		omnipotent crazy final deathless immortal elite
		leet 1337 bloody fearless headless screaming insane
		brutal legendary space frozen flaming burning lazy
		mighty flayed hidden secret lost mystery glorious
		nude naked bare first radiant martian fallen bog
		wandering dank demonic satanic invisible based woke
		deadly lethal heroic evil majestic luminous ethereal
		perfect first fantastic special great steel insane
		royal imperial celestial cosmic mystic sublime
	]]

	if xXx then a:lpush('xXx_') end

	if useadj then
		var len = rnd(uint8,1,3) 
		for i = 0, len do
			var sz = a.sz
			a:ppush(adjs[rnd(intptr,0,[adjs.type.N])])
			if punct ~= nil then a:push(punct, 1) end
			if caps == 1 then
				a.buf[sz] = lib.str.cupcase(a.buf[sz])
			end
		end
	end
	var nounct = rnd(uint8,1,3) 
	for i = 0, nounct do
		var sz = a.sz
		a:ppush(nouns[rnd(intptr,0,[nouns.type.N])])
		if punct ~= nil and i+1 ~= nounct then a:push(punct, 1) end
		if caps == 1 then
			a.buf[sz] = lib.str.cupcase(a.buf[sz])
		end
	end

	if leet or caps == 9 then for i=start, a.sz do
		if caps == 9 and rnd(uint8,0,5)==0 then
			a.buf[i] = lib.str.cupcase(a.buf[i])
		end
		if leet then 
			switch lib.str.cdowncase(a.buf[i]) do
				case [uint8]([string.byte('e')]) then a.buf[i] = @'3' end
				case [uint8]([string.byte('i')]) then a.buf[i] = @'1' end
				case [uint8]([string.byte('l')]) then a.buf[i] = @'1' end
				case [uint8]([string.byte('t')]) then a.buf[i] = @'7' end
				case [uint8]([string.byte('s')]) then a.buf[i] = @'5' end
				case [uint8]([string.byte('o')]) then a.buf[i] = @'0' end
				case [uint8]([string.byte('b')]) then a.buf[i] = @'6' end
			end
		end
	end end

	if (nounct == 1 and not useadj) or rnd(uint8, 0, 5) == 0 then
		if punct ~= nil then a:push(punct, 1) end
		a:ipush(rnd(uint32,0,lib.math.pow(10,rnd(uint8,1,4))))
	end

	if xXx then a:lpush('_xXx') end

end

local terra 
suggest_domain(a: &lib.str.acc)
	var words = splitwords [[
		flop slop hop wiggle wriggle bug snoot boop jorts horse rad
		witch witches cum code spank grump grumps slap spoop spoopy
		spook wobble flip jock nerd dope dork slab drug funk gay
		hex node snack weed pot slug worm fur fuzz fuzzy game gamer
		rock smack drank wack wild sexy hot sin cock fuck piss man
		wank fae weird woke slurp spine skull fail elf elves mom
		dad dog cat kitten snake troll top bottom chungus dong wang
		420 hog lover lovers best worst love hate big bigger tiny
		little teeny spunky jazz wrack rump kink kinky crack meth
		whore cam live over under turbo pizza rat rats crotch crank
		chunky funky butt grab grabber grabbers thief steal slave
		slaves hug hugs hag hags hogs wimp thieves wizard wizards
		pussy pansy dark doom stank spunk dumb rage worship orb
		terror fear blood slime slab warp waggle tit boob bird derp
		birb goat horde masto mastodon social global tweet post
		house home prison jail box pit hole haven town trump putin
		truth liberty zone land ranch butt butts sex pimp cop mail
		slut goblin goblins no good bad only gtfo electro electric
		dragon space mars earth venus neptune pluto saturn star
		moon lunar catastrophe catastro cuck honk war lap cuddle
		planet pride
	]]
	var tlds = splitwords [[
		tld club town space xxx house land ranch horse com io online
		shop site vip ltd win men lgbt cat adult army analytics art
		associates bar bible biz black blog broker cam camp careers
		catering church city coop dad date dating direct diy dog
		duck dot enterprises esq estate expert express fail farm foo
		forsale fun fund forum foundation gay global golf gop guru
		group hangout hot industries international info investments
		jobs land law life limited live lol mom network now party
		porn productions pub rehab rocks school sex sexy singles
		social software solutions spot store sucks supplies cuck
		uwu systems university vacations ventures wang website work
		wow wtf world xyz soy live gym park worship orb zone mail
		war honk derp planet pride
	]]
	var sub = rnd(uint8,0,10) == 0
	if sub then a:ppush(words[rnd(intptr,0,[words.type.N])]):lpush('.') end
	a:ppush(words[rnd(intptr,0,[words.type.N])])
	if rnd(uint8,0,3) == 0 or not sub then
		a:ppush(words[rnd(intptr,0,[words.type.N])])
	end
	a:lpush('.'):ppush(tlds[rnd(intptr,0,[tlds.type.N])])
end

local push_num_field = macro(function(acc,name,lbl,min,max,value,disable)
	name = name:asvalue()
	lbl = lbl:asvalue()
	local start = '<div class="elem small">'
	local enabled = start .. string.format('<label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name)
	local disabled = start .. string.format('<label>%s</label><div class="txtbox">', lbl)
	return quote
		var decbuf: int8[21]
		if disable then
			acc:lpush([disabled])
			   :push(lib.math.decstr(value, &decbuf[20]),0):lpush('</div></div>')
		else
			acc:lpush([enabled]):push(lib.math.decstr(min, &decbuf[20]),0)
			   :lpush('" max="'):push(lib.math.decstr(max, &decbuf[20]),0)
			   :lpush('" value="'):push(lib.math.decstr(value, &decbuf[20]),0):lpush('"></div>')
		end
	end
end)

local input_pusher = function(kind,wrap,uniq)
	local fn = terra(acc: &lib.str.acc, id: pstr, name: pstr, val: pstr, lbl: pstr, on: bool, enabled: bool, class: pstr)
		if wrap then acc:lpush('<label>') end
		acc:lpush(['<input type="'..kind..'" name="']):ppush(name)
		if not wrap then
			acc:lpush('" id="'):ppush(id)
			if uniq then acc:lpush('-'):ppush(val) end
		end
		if val:ref()   then acc:lpush('" value="'):ppush(val) end
		if class:ref() then acc:lpush('" class="'):ppush(class) end
		acc:lpush('"')
		if on then acc:lpush(' checked') end
		if not enabled then acc:lpush(' disabled') end
		acc:lpush('>')
		if not wrap then acc:lpush('<label for="'):ppush(id)
		                 if uniq then acc:lpush('-'):ppush(val) end
		                 acc:lpush('">')
		            else acc:lpush(' ') end
		acc:ppush(lbl):lpush('</label>')
	end
	fn.name = string.format('push-input-element<%q>',kind)
	return fn
end

local push_checkbox = input_pusher('checkbox',true,false)
local push_pickbox = input_pusher('checkbox',false,false)
local push_radio = input_pusher('radio',false,true)

local mode_local, mode_remote, mode_staff, mode_peers, mode_peons, mode_all = 0,1,2,3,4,5
local terra 
render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
	if path.ct >= 3 then
		var uid, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
		if not ok then goto e404 end
		var user = co.srv:actor_fetch_uid(uid)
		-- FIXME allow xids as well, for manual queries
		if not user then goto e404 end
		defer user:free()
		if not co.who:overpowers(user.ptr) then goto e403 end

		if path.ct == 4 then
			if path(3):cmp(lib.str.lit'cred') then
				var pg = co:stra(1024)
				pg:lpush('<div class="context">editing credentials for user <a href="/conf/users/'):rpush(path(2)):lpush('">'):push(user(0).xid,0):lpush('</a></div>')
				var credmgr = lib.render.conf.sec(co, uid)
				pg:ppush(credmgr)
				--credmgr:free()
				return pg:finalize()
			else goto e404 end
		elseif path.ct == 3 then
			var cinp = co:stra(256)
			cinp:lpush('<div class="elem-group">')
			if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then
				var max = co.who.rights.rank
				if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end
				var min = co.srv.cfg.nranks
				if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end

				push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id)
			end
			if co.who.rights.powers.herald() then
				var sanitized: pstr
				if user.ptr.epithet == nil
					then sanitized = pstr {ptr='', ct=0}
					else sanitized = lib.html.sanitize(&co.srv.pool,cs(user.ptr.epithet),true)
				end
				cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>')
				--if user.ptr.epithet ~= nil then sanitized:free() end
			end
			if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then
				var min: uint32 = 0
				if not (co.who.rights.powers.discipline() or
					co.who.rights.powers.demote() and co.who.rights.powers.invite())
						then min = user.ptr.rights.invites end
				var max: uint32 = co.srv.cfg.maxinvites
				if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end

				push_num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites, false)
			end
			if co.who.rights.powers.elevate() or co.who.rights.powers.demote() then
				var max: uint32 = 5000
				if not co.who.rights.powers.elevate() then max = user.ptr.rights.quota end
				var min: uint32 = 0
				if not co.who.rights.powers.demote() then min = user.ptr.rights.quota end

				push_num_field(cinp, 'quota', 'quota', min, max, user.ptr.rights.quota, user.ptr.id == co.who.id and co.who.rights.rank ~= 1)
			end
			cinp:lpush('</div><div class="elem"><div class="check-panel">')

			if user.ptr.id ~= co.who.id and
			   ((user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or
				(user.ptr.rights.rank >  0 and co.who.rights.powers.demote())) then
				push_checkbox(&cinp, 'staff', 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null())
			end

			cinp:lpush('</div></div>')

			if (co.who.rights.powers.elevate() or
			   co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then
				var map = array([lib.store.powmap])
				cinp:lpush('<details><summary>powers</summary><div class="pick-list">')
					for i=0, [map.type.N] do
						if (co.who.rights.powers and map[i].val):sz() > 0 then
							var on = (user.ptr.rights.powers and map[i].val):sz() > 0
							var enabled = (     on  and co.who.rights.powers.demote() ) or
										  ((not on) and co.who.rights.powers.elevate())
							var namea: lib.str.acc namea:pcompose(&co.srv.pool,'power-', map[i].name)
							var name = namea:finalize()
							push_pickbox(&cinp, name, 'power', map[i].name, map[i].name, on, enabled, pstr.null())
							--name:free()
						end
					end
				cinp:lpush('</div></details>')
			end

			if co.who.id ~= uid and co.who.rights.powers.purge() then
				var purgeconf = co:stra(48)
				var purgestrs = array(
					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
					'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory',
					'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy',
					'perihelion', 'aphelion'
				)
				for i=0,3 do
					purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0)
					if i ~= 2 then purgeconf:lpush('-') end
				end
				cinp:lpush('<details><summary>purge account</summary><p>you have the authority to destroy this account and all its associated content irreversibly and irretrievably. if you really wish to apply such an extreme sanction, enter the confirmation string <strong style="user-select:none">'):push(purgeconf.buf,purgeconf.sz):lpush('</strong> below and press the β€œalter” button to begin the process.</p><div class="elem"><label for="purge">purge confirmation string</label><input type="text" id="purge" name="purgekey"></div><input type="hidden" name="purgestr" value="'):push(purgeconf.buf,purgeconf.sz):lpush('"></details>')
				--purgeconf:free()
			end

			-- TODO black mark system? e.g. resolution option for badthink reports
			-- adds a black mark to the offending user; they can be automatically banned
			-- or brought up for review after a certain number of offenses; possibly lower
			-- set of default privs for marked users

			var cinpp = cinp:finalize() --defer cinpp:free()
			var unym = co:stra(64)
			unym:lpush('<a href="/')
			if user(0).origin ~= 0 then unym:lpush('@') end
			do var sanxid = lib.html.sanitize(&co.srv.pool,user(0).xid, true)
				unym:ppush(sanxid)
				--sanxid:free()
			end
			unym:lpush('" class="id">')
			lib.render.nym(user.ptr,0,&unym,false)
			unym:lpush('</a>')
			var ctlbox = data.view.conf_user_ctl {
				name = unym:finalize();
				inputcontent = cinpp;
				btns = pstr{'',0};
			}
			if co.who.id ~= uid and co.who.rights.powers.cred() then
				ctlbox.btns = lib.str.acc{}:pcompose(&co.srv.pool,'<a class="button" href="/conf/users/',path(2),'/cred">security &amp; credentials</a>'):finalize()
			end
			var pg = co:stra(512)
			ctlbox:append(&pg)
			--ctlbox.name:free()
			--if ctlbox.btns.ct > 0 then ctlbox.btns:free() end

			return pg:finalize()
		end
	else
		var modes = arrayof(pstr,'local', 'remote', 'staff', 'titled', 'peons', 'all')
		var idbuf: int8[lib.math.shorthand.maxlen]
		var ulst = co:stra(256)
		var mode: uint8 = mode_local
		var modestr = co:pgetv('show')
		ulst:lpush('<div style="text-align: right"><em>showing ')
		for i=0,[modes.type.N] do
			if modestr:ref() and modes[i]:cmp(modestr) then mode = i end
		end
		for i=0,[modes.type.N] do
			if i > 0 then ulst:lpush(' Β· ') end
			if mode == i then
				ulst:lpush('<strong>'):ppush(modes[i]):lpush('</strong>')
			else
				ulst:lpush('<a href="?show='):ppush(modes[i]):lpush('">')
					:ppush(modes[i]):lpush('</a>')
			end
		end
		var users: lib.mem.lstptr(lib.store.actor)
		if mode == mode_local then
			users = co.srv:actor_enum_local()
		else
			users = co.srv:actor_enum()
		end
		ulst:lpush('</em></div>')
		ulst:lpush('<ul class="directory">')
		for i=0,users.ct do var usr = users(i).ptr
			if mode == mode_staff and usr.rights.rank == 0 then goto skip
			elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip
			elseif mode == mode_remote and usr.origin == 0 then goto skip 
			elseif mode == mode_peers and usr.epithet == nil then goto skip end
			var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0])
			ulst:lpush('<li>')
			if usr.rights.rank ~= 0 then
				ulst:lpush('<span class="regalia">')
				regalia(&ulst, usr.rights.rank)
				ulst:lpush('</span>')
			end
			if co.who:overpowers(usr) then
				ulst:lpush('<a class="id" href="users/'):push(&idbuf[0],idlen):lpush('">')
				lib.render.nym(usr, 0, &ulst, false)
				ulst:lpush('</a></li>')
			else
				ulst:lpush('<span class="id">')
				lib.render.nym(usr, 0, &ulst, false)
				ulst:lpush('</span></li>')
			end
		::skip::end
		ulst:lpush('</ul>')

		if co.who.rights.powers.invite() or co.who.rights.invites > 0 then
			ulst:lpush('<details><summary>create new user</summary><form method="post"><div class="elem"><label for="handle">handle</label><input type="text" name="handle" id="handle" placeholder="')
			suggest_handle(&ulst)
			ulst:lpush('"></div><button name="act" value="create">create</button></form></details>')
		end
		ulst:lpush('<details><summary>instantiate remote actor</summary><form method="post"><div class="elem"><label for="xid">xid</label><input type="text" name="xid" id="xid" placeholder="')
		suggest_handle(&ulst) ulst:lpush('@') suggest_domain(&ulst)
		ulst:lpush('"></div><button name="act" value="inst">instantiate</button></form></details>')

		return ulst:finalize()
	end
	do return pstr.null() end
	::e404:: co:complain(404, 'not found', 'there is no user or resource by that identifier on this server') goto quit
	::e403:: co:complain(403, 'forbidden', 'you do not have sufficient authority to control that resource')

	::quit:: return pstr.null()
end

return render_conf_users