parsav  users.t at [78b0198f09]

File render/conf/users.t artifact 4bed391611 part of check-in 78b0198f09


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

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 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, 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(name)
			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(name)
		                 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

		var cinp: lib.str.acc cinp:init(256)
		var clnk: lib.str.acc clnk:init(512)
		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(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', 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.privmap])
			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].priv):sz() > 0 then
						var on = (user.ptr.rights.powers and map[i].priv):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:compose('power-', map[i].name)
						var name = namea:finalize()
						push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null())
						name:free()
					end
				end
			cinp:lpush('</div></details>')
		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 clnkp: pstr
		if clnk.sz > 0 then clnkp = clnk:finalize() else
			clnk:free()
			clnkp = pstr { ptr='', ct=0 }
		end
		var unym: lib.str.acc unym:init(64)
		unym:lpush('<a href="/')
		if user(0).origin ~= 0 then unym:lpush('@') end
		do var sanxid = lib.html.sanitize(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 pg = data.view.conf_user_ctl {
			name = unym:finalize();
			inputcontent = cinpp;
			linkcontent = clnkp;
		}
		var ret = pg:tostr()
		pg.name:free()
		if clnkp.ct > 0 then clnkp:free() end
		return ret
	else
		var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all')
		var idbuf: int8[lib.math.shorthand.maxlen]
		var ulst: lib.str.acc ulst:init(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="user-list">')
		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>')
		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