parsav  Check-in [db4c5fd644]

Overview
Comment:start work on user mgmt
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: db4c5fd644227803a989a7a70a8421291451df8360d4a3bd3ce6be854629adb3
User & Date: lexi on 2020-12-31 02:18:38
Other Links: manifest | tags
Context
2021-01-01
04:33
add live updates, system to only update when necessary almost works check-in: 24ec409083 user: lexi tags: trunk
2020-12-31
02:18
start work on user mgmt check-in: db4c5fd644 user: lexi tags: trunk
00:15
add lots more shit check-in: d4ecea913f user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [af6b4187ca] to [35848d4bf0].

   300    300   				$1::bigint, case when $2::text = '' then null else $2::text end,
   301    301   				$3::text, $4::text, 
   302    302   				now(), now(), array[]::bigint[], array[]::bigint[]
   303    303   			) returning id
   304    304   		]]; -- TODO array handling
   305    305   	};
   306    306   
          307  +	post_destroy_prepare = {
          308  +		params = {uint64}, cmd = true, sql = [[
          309  +			update parsav_posts set
          310  +				parent = (select parent from parsav_posts where id = $1::bigint limit 1)
          311  +			where parent = $1::bigint
          312  +		]]
          313  +	};
          314  +
          315  +	post_destroy = {
          316  +		params = {uint64}, cmd = true, sql = [[
          317  +			delete from parsav_posts where id = $1::bigint
          318  +		]]
          319  +	};
          320  +	
   307    321   	post_fetch = {
   308    322   		params = {uint64}, sql = [[
   309    323   			select a.origin is null,
   310    324   				p.id, p.author, p.subject, p.acl, p.body,
   311    325   				extract(epoch from p.posted    )::bigint,
   312    326   				extract(epoch from p.discovered)::bigint,
   313    327   				extract(epoch from p.edited    )::bigint,
................................................................................
   773    787   			end
   774    788   		end
   775    789   	end
   776    790   
   777    791   	return powers
   778    792   end
   779    793   
          794  +
          795  +local txdo = terra(src: &lib.store.source)
          796  +	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin')
          797  +	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
          798  +		lib.dbg('beginning pgsql transaction')
          799  +		return true
          800  +	else
          801  +		lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res))
          802  +		return false
          803  +	end
          804  +end
          805  +
          806  +local txdone = terra(src: &lib.store.source)
          807  +	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end')
          808  +	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
          809  +		lib.dbg('completing pgsql transaction')
          810  +		return true
          811  +	else
          812  +		lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res))
          813  +		return false
          814  +	end
          815  +end
          816  +
   780    817   local b = `lib.store.backend {
   781    818   	id = "pgsql";
   782    819   	open = [terra(src: &lib.store.source): &opaque
   783    820   		lib.report('connecting to postgres database: ', src.string.ptr)
   784    821   		var [con] = lib.pq.PQconnectdb(src.string.ptr)
   785    822   		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
   786    823   			lib.warn('postgres backend connection failed')
................................................................................
   803    840   			return nil
   804    841   		end
   805    842   
   806    843   		return con
   807    844   	end];
   808    845   
   809    846   	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
          847  +
          848  +	tx_enter = txdo, tx_complete = txdone;
   810    849   
   811    850   	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
   812    851   		var [con] = [&lib.pq.PGconn](src.handle)
   813    852   		if mode == lib.store.prepmode.full then [prep]
   814    853   		elseif mode == lib.store.prepmode.conf or
   815    854   		       mode == lib.store.prepmode.admin then 
   816    855   			queries.conf_get.prep(con)
................................................................................
   837    876   		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
   838    877   			lib.report('successfully wiped out everything parsav-related in database')
   839    878   			return true
   840    879   		else
   841    880   			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
   842    881   			return false
   843    882   		end
   844         -	end];
   845         -
   846         -	tx_enter = [terra(src: &lib.store.source)
   847         -		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin')
   848         -		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
   849         -			lib.dbg('beginning pgsql transaction')
   850         -			return true
   851         -		else
   852         -			lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res))
   853         -			return false
   854         -		end
   855         -	end];
   856         -
   857         -	tx_complete = [terra(src: &lib.store.source)
   858         -		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end')
   859         -		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
   860         -			lib.dbg('completing pgsql transaction')
   861         -			return true
   862         -		else
   863         -			lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res))
   864         -			return false
   865         -		end
   866    883   	end];
   867    884   
   868    885   	conf_get = [terra(src: &lib.store.source, key: rawstring)
   869    886   		var r = queries.conf_get.exec(src, key)
   870    887   		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
   871    888   			defer r:free()
   872    889   			return r:String(0,0)
................................................................................
  1014   1031   	): uint64
  1015   1032   		var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 
  1016   1033   		if r.sz == 0 then return 0 end
  1017   1034   		defer r:free()
  1018   1035   		var id = r:int(uint64,0,0)
  1019   1036   		return id
  1020   1037   	end];
         1038  +
         1039  +	post_destroy = [terra(
         1040  +		src: &lib.store.source,
         1041  +		post: uint64
         1042  +	): {}
         1043  +		txdo(src)
         1044  +			queries.post_destroy_prepare.exec(src, post)
         1045  +			queries.post_destroy.exec(src, post)
         1046  +		txdone(src)
         1047  +	end];
  1021   1048   
  1022   1049   	post_fetch = [terra(
  1023   1050   		src: &lib.store.source,
  1024   1051   		post: uint64
  1025   1052   	): lib.mem.ptr(lib.store.post)
  1026   1053   		var r = queries.post_fetch.exec(src, post)
  1027   1054   		if r.sz == 0 then return [lib.mem.ptr(lib.store.post)].null() end

Modified parsav.t from [022b1bf037] to [90c24eca6f].

     4      4   local buildopts, buildargs = util.parseargs{...}
     5      5   config = dofile('config.lua')
     6      6   
     7      7   lib = {
     8      8   	init = {}, util = util;
     9      9   	load = function(lst)
    10     10   		for _, l in pairs(lst) do
           11  +			io.stdout:write(' · processing module \27[1m' .. l ..'\27[m… ')
    11     12   			local path = {}
    12     13   			for m in l:gmatch('([^:]+)') do path[#path+1]=m end
    13     14   			local tgt = lib
    14     15   			for i=1,#path-1 do
    15     16   				if tgt[path[i]] == nil then tgt[path[i]] = {} end
    16     17   				tgt = tgt[path[i]]
    17     18   			end
    18         -			tgt[path[#path]:gsub('-','_')] = terralib.loadfile(l:gsub(':','/') .. '.t')()
           19  +			local chunk = terralib.loadfile(l:gsub(':','/') .. '.t')
           20  +			if chunk ~= nil then
           21  +				tgt[path[#path]:gsub('-','_')] = chunk()
           22  +				print(' \27[1m[ \27[32mok\27[;1m ]\27[m')
           23  +			else
           24  +				print(' \27[1m[\27[31mfail\27[;1m]\27[m')
           25  +				os.exit(2)
           26  +			end
    19     27   		end
    20     28   	end;
    21     29   	loadlib = function(name,hdr)
    22     30   		local p = config.pkg[name]
    23     31   		-- for _,v in pairs(p.dylibs) do
    24     32   		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
    25     33   		-- end
................................................................................
   431    439   	'render:user-page';
   432    440   	'render:timeline';
   433    441   
   434    442   	'render:docpage';
   435    443   
   436    444   	'render:conf:profile';
   437    445   	'render:conf:sec';
          446  +	'render:conf:users';
   438    447   	'render:conf';
   439    448   	'route';
   440    449   }
   441    450   
   442    451   do
   443    452   	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
   444    453   	terra version() lib.io.send(1, p, [#p]) end
................................................................................
   609    618   if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end
   610    619   
   611    620   for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
   612    621   local linkargs_d = linkargs -- controller is not multithreaded
   613    622   if config.posix then
   614    623   	linkargs_d[#linkargs_d+1] = '-pthread'
   615    624   end
   616         -holler('linking with args',util.dump(linkargs))
   617    625   
   618         -terralib.saveobj('parsavd'..suffix, { main = entry_daemon }, linkargs_d, target)
          626  +holler(' → linking \27[1mparsav\27[m with "' .. table.concat(linkargs,' ') .. '"')
   619    627   terralib.saveobj('parsav' ..suffix, { main = lib.mgtool }, linkargs, target)
          628  +
          629  +holler(' → linking \27[1mparsavd\27[m with "' .. table.concat(linkargs_d,' ') .. '"')
          630  +terralib.saveobj('parsavd'..suffix, { main = entry_daemon }, linkargs_d, target)

Added render/conf/users.t version [6e4ba75dd2].

            1  +-- vim: ft=terra
            2  +local pstr = lib.mem.ptr(int8)
            3  +local pref = lib.mem.ref(int8)
            4  +
            5  +local terra cs(s: rawstring)
            6  +	return pstr { ptr = s, ct = lib.str.sz(s) }
            7  +end
            8  +
            9  +local terra 
           10  +render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
           11  +	if path.ct == 2 then
           12  +		var uid, ok = lib.math.shorthand.parse(path(1).ptr,path(1).ct)
           13  +		var user = co.srv:actor_fetch_uid(uid)
           14  +		if not user then goto e404 end
           15  +		var islinkct = false
           16  +		var cinp: lib.str.acc
           17  +		var clnk: lib.str.acc clnk:compose('<hr>')
           18  +
           19  +		var cinpp = cinp:finalize() defer cinpp:free()
           20  +		var clnkp: pstr
           21  +		if islinkct then clnkp = clnk:finalize() else
           22  +			clnk:free()
           23  +			clnkp = pstr { ptr='', ct=0 }
           24  +		end
           25  +		var pg = data.view.conf_user_ctl {
           26  +			name = cs(user(0).handle);
           27  +			inputcontent = cinpp;
           28  +			linkcontent = clnkp;
           29  +		}
           30  +		var ret = pg:tostr()
           31  +		if islinkct then clnkp:free() end
           32  +		return ret
           33  +	else
           34  +
           35  +	end
           36  +	do return pstr.null() end
           37  +	::e404:: co:complain(404, 'not found', 'there is no user or resource by that identifier on this server')
           38  +
           39  +	do return pstr.null() end
           40  +end
           41  +
           42  +return render_conf_users

Modified route.t from [2469fad253] to [a9eb70a00e].

   175    175   	if not post then
   176    176   		co:complain(404, 'post not found', 'no such post is known to this server')
   177    177   		return
   178    178   	end
   179    179   	defer post:free()
   180    180   
   181    181   	if path.ct == 3 then
   182         -		if path(2):cmp(lib.str.lit 'edit') then
   183         -			if post(0).author ~= co.who.id then
   184         -				co:complain(403, 'forbidden', 'you cannot edit other people\'s posts')
   185         -				return
   186         -			end
   187         -
          182  +		var lnk: lib.str.acc lnk:compose('/post/', path(1))
          183  +		var lnkp = lnk:finalize() defer lnkp:free()
          184  +		if post(0).author ~= co.who.id then
          185  +			co:complain(403, 'forbidden', 'you cannot alter other people\'s posts')
          186  +			return
          187  +		elseif path(2):cmp(lib.str.lit 'edit') then
   188    188   			if meth == method.get then
   189    189   				lib.render.compose(co, post.ptr, nil)
   190    190   				return
   191    191   			elseif meth == method.post then
   192    192   				var newbody = co:postv('post')._0
   193    193   				var newacl = co:postv('acl')._0
   194    194   				var newsubj = co:postv('subject')._0
   195    195   				if newbody ~= nil then post(0).body = newbody end
   196    196   				if newacl  ~= nil then post(0).acl = newacl end
   197    197   				if newsubj ~= nil then post(0).subject = newsubj end
   198    198   				post(0):save(true)
   199         -
   200         -				var lnk: lib.str.acc lnk:compose('/post/', path(1))
   201         -				co:reroute(lnk.buf)
   202         -				lnk:free()
          199  +				co:reroute(lnkp.ptr)
   203    200   			end
   204    201   			return
          202  +		elseif path(2):cmp(lib.str.lit 'del') then
          203  +			if meth == method.get then
          204  +				var conf = data.view.confirm {
          205  +					title = lib.str.plit 'delete post';
          206  +					query = lib.str.plit 'are you sure you want to delete this post?';
          207  +					cancel = lnkp
          208  +				}
          209  +				var body = conf:tostr() defer body:free()
          210  +				co:stdpage([lib.srv.convo.page] {
          211  +					title = lib.str.plit 'post :: delete';
          212  +					class = lib.str.plit 'query';
          213  +					body = body; cache = false;
          214  +				})
          215  +				return
          216  +			elseif meth == method.post then
          217  +				var act = co:ppostv('act')
          218  +				if act:cmp(lib.str.plit 'confirm') then
          219  +					post(0).source:post_destroy(post(0).id)
          220  +					co:reroute('/') -- TODO maybe return to parent or conversation if possible
          221  +					return
          222  +				else goto badop end
          223  +			end
   205    224   		else goto badurl end
   206    225   	end
   207    226   
   208         -	if meth == method.post then
   209         -		co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful')
   210         -		return
   211         -	end
          227  +	if meth == method.post then goto badop end
   212    228   
   213    229   	lib.render.tweet_page(co, path, post.ptr)
   214    230   	do return end
   215    231   
   216         -	::badurl:: co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality')
          232  +	::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end
          233  +	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
   217    234   end
   218    235   
   219    236   terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
   220    237   	var msg = pstring.null()
   221    238   	if meth == method.post and path.ct >= 1 then
   222    239   		var user_refresh = false var fail = false
   223    240   		if path(1):cmp(lib.str.lit 'profile') then
................................................................................
   226    243   			co.who.nym = co:postv('nym')._0
   227    244   			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
   228    245   			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
   229    246   			co.who.source:actor_save(co.who)
   230    247   			msg = lib.str.plit 'profile changes saved'
   231    248   			--user_refresh = true -- not really necessary here, actually
   232    249   		elseif path(1):cmp(lib.str.lit 'srv') then
          250  +			if not co.who.rights.powers.config() then goto nopriv end
          251  +		elseif path(1):cmp(lib.str.lit 'brand') then
          252  +			if not co.who.rights.powers.rebrand() then goto nopriv end
   233    253   		elseif path(1):cmp(lib.str.lit 'users') then
          254  +			if not co.who.rights.powers:affect_users() then goto nopriv end
          255  +
   234    256   		elseif path(1):cmp(lib.str.lit 'sec') then
   235    257   			var act = co:ppostv('act')
   236    258   			if act:cmp(lib.str.plit 'invalidate') then
   237    259   				lib.dbg('setting user\'s cookie validation time to now')
   238    260   				co.who.source:auth_sigtime_user_alter(co.who.id, lib.osclock.time(nil))
   239    261   				-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
   240    262   				co:installkey('/conf/sec',co.aid)
................................................................................
   250    272   		var go,golen = co:getv('go')
   251    273   		if not fail and go ~= nil then
   252    274   			co:reroute(go)
   253    275   			return
   254    276   		end
   255    277   	end
   256    278   	lib.render.conf(co,path,msg)
          279  +	do return end
          280  +
          281  +	::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action')
   257    282   end
   258    283   
   259    284   do local branches = quote end
   260    285   	local filename, flen = symbol(&int8), symbol(intptr)
   261    286   	local page = symbol(lib.http.page)
   262    287   	local send = label()
   263    288   	local storage = data.stmap

Modified static/style.scss from [9b25bded91] to [ada3763759].

    68     68   	padding: 0.1in 0.2in;
    69     69   	border: 1px solid black;
    70     70   	color: tone(25%);
    71     71   	text-shadow: 1px 1px black;
    72     72   	text-decoration: none;
    73     73   	text-align: center;
    74     74   	cursor: default;
           75  +	user-select: none;
    75     76   	background: linear-gradient(to bottom,
    76     77   		tone(-47%),
    77     78   		tone(-50%) 15%,
    78     79   		tone(-50%) 75%,
    79     80   		tone(-53%)
    80     81   	);
    81     82   	&:hover, &:focus {
................................................................................
   339    340   .message {
   340    341   	@extend %box;
   341    342   	display: block;
   342    343   	width: 4in;
   343    344   	margin:auto;
   344    345   	padding: 0.5in;
   345    346   	text-align: center;
          347  +	menu:first-of-type { margin-top: 0.3in; }
   346    348   }
   347    349   
   348    350   div.login {
   349    351   	@extend %box;
   350    352   	width: 4in;
   351    353   	padding: 0.4in;
   352    354   	> .msg {
................................................................................
   522    524   		background: linear-gradient(to right, tone(-50%), transparent);
   523    525   		margin-left: -0.4in;
   524    526   		padding-left: 0.2in;
   525    527   		text-shadow: 0 2px 0 black;
   526    528   	}
   527    529   }
   528    530   
          531  +menu { all: unset; display: block; }
   529    532   body.conf main {
   530    533   	display: grid;
   531    534   	grid-template-columns: 2in 1fr;
   532    535   	grid-template-rows: max-content 1fr;
   533    536   	> menu {
   534    537   		margin-left: -0.25in;
   535    538   		grid-column: 1/2; grid-row: 1/2;
................................................................................
   606    609   	}
   607    610   	&.vertical-float {
   608    611   		flex-flow: column;
   609    612   		float: right;
   610    613   		width: 40%;
   611    614   		margin-left: 0.1in;
   612    615   	}
   613         -	> %button { display: block; margin: 2px; flex-grow: 1 }
          616  +	> %button {
          617  +		flex-basis: 0;
          618  +		flex-grow: 1;
          619  +		display: block; margin: 2px;
          620  +	}
   614    621   }
   615    622   
   616    623   .check-panel {
   617    624   	display: flex;
   618    625   	flex-flow: row wrap;
   619    626   	> label {
   620    627   		display: block;

Modified store.t from [004846cca6] to [d79d41c9fe].

   335    335   			-- uid: uint64
   336    336   	auth_sigtime_user_alter: {&m.source, uint64, m.timepoint} -> {}
   337    337   			-- uid: uint64
   338    338   			-- timestamp: timepoint
   339    339   
   340    340   	post_save: {&m.source, &m.post} -> {}
   341    341   	post_create: {&m.source, &m.post} -> uint64
          342  +	post_destroy: {&m.source, uint64} -> {}
   342    343   	post_fetch: {&m.source, uint64} -> lib.mem.ptr(m.post)
   343    344   	post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
   344    345   	post_attach_ctl: {&m.source, uint64, uint64, bool} -> {}
   345    346   		-- attaches or detaches an existing database artifact
   346    347   			-- post id: uint64
   347    348   			-- artifact id: uint64
   348    349   			-- detach: bool

Added view/conf-user-ctl.tpl version [9830040aea].

            1  +<form method="post">
            2  +	<div class="elem">
            3  +		<label>user</label>
            4  +		<div class="txtbox">@name</div>
            5  +	</div>
            6  +	@inputcontent
            7  +	<button>alter</button>
            8  +	@linkcontent
            9  +</form>

Modified view/confirm.tpl from [9198c794e9] to [0d2952df9c].

     1         -<form class="message">
            1  +<form class="message" method="post">
     2      2   	<img class="icon" src="/s/query.webp">
     3      3   	<h1>@title</h1>
     4      4   	<p>@query</p>
     5      5   	<menu class="horizontal choice">
     6      6   		<a class="button" href="@:cancel">cancel</a>
     7      7   		<button name="act" value="confirm">confirm</button>
     8      8   	</menu>
     9      9   </form>

Modified view/load.lua from [212041720e] to [dd2878563c].

    13     13   	'login-username';
    14     14   	'login-challenge';
    15     15   
    16     16   	'conf';
    17     17   	'conf-profile';
    18     18   	'conf-sec';
    19     19   	'conf-sec-credmg';
           20  +	'conf-user-ctl';
    20     21   }
    21     22   
    22     23   local ingest = function(filename)
    23     24   	local hnd = io.open(path..'/'..filename)
    24     25   	local txt = hnd:read('*a')
    25     26   	io.close(hnd)
    26     27   	txt = txt:gsub('([^\\])!%b[]', '%1')