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
301
302
303
304
305
306














307
308
309
310
311
312
313
...
773
774
775
776
777
778
779























780
781
782
783
784
785
786
...
803
804
805
806
807
808
809


810
811
812
813
814
815
816
...
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
....
1014
1015
1016
1017
1018
1019
1020










1021
1022
1023
1024
1025
1026
1027
				$1::bigint, case when $2::text = '' then null else $2::text end,
				$3::text, $4::text, 
				now(), now(), array[]::bigint[], array[]::bigint[]
			) returning id
		]]; -- TODO array handling
	};















	post_fetch = {
		params = {uint64}, sql = [[
			select a.origin is null,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
................................................................................
			end
		end
	end

	return powers
end
























local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
			lib.warn('postgres backend connection failed')
................................................................................
			return nil
		end

		return con
	end];

	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];



	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
		var [con] = [&lib.pq.PGconn](src.handle)
		if mode == lib.store.prepmode.full then [prep]
		elseif mode == lib.store.prepmode.conf or
		       mode == lib.store.prepmode.admin then 
			queries.conf_get.prep(con)
................................................................................
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.report('successfully wiped out everything parsav-related in database')
			return true
		else
			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	tx_enter = [terra(src: &lib.store.source)
		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin')
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.dbg('beginning pgsql transaction')
			return true
		else
			lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	tx_complete = [terra(src: &lib.store.source)
		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end')
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.dbg('completing pgsql transaction')
			return true
		else
			lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	conf_get = [terra(src: &lib.store.source, key: rawstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:String(0,0)
................................................................................
	): uint64
		var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 
		if r.sz == 0 then return 0 end
		defer r:free()
		var id = r:int(uint64,0,0)
		return id
	end];











	post_fetch = [terra(
		src: &lib.store.source,
		post: uint64
	): lib.mem.ptr(lib.store.post)
		var r = queries.post_fetch.exec(src, post)
		if r.sz == 0 then return [lib.mem.ptr(lib.store.post)].null() end







>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>







300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
...
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
...
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
...
876
877
878
879
880
881
882






















883
884
885
886
887
888
889
....
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
				$1::bigint, case when $2::text = '' then null else $2::text end,
				$3::text, $4::text, 
				now(), now(), array[]::bigint[], array[]::bigint[]
			) returning id
		]]; -- TODO array handling
	};

	post_destroy_prepare = {
		params = {uint64}, cmd = true, sql = [[
			update parsav_posts set
				parent = (select parent from parsav_posts where id = $1::bigint limit 1)
			where parent = $1::bigint
		]]
	};

	post_destroy = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_posts where id = $1::bigint
		]]
	};
	
	post_fetch = {
		params = {uint64}, sql = [[
			select a.origin is null,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
................................................................................
			end
		end
	end

	return powers
end


local txdo = terra(src: &lib.store.source)
	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin')
	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
		lib.dbg('beginning pgsql transaction')
		return true
	else
		lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res))
		return false
	end
end

local txdone = terra(src: &lib.store.source)
	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end')
	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
		lib.dbg('completing pgsql transaction')
		return true
	else
		lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res))
		return false
	end
end

local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
			lib.warn('postgres backend connection failed')
................................................................................
			return nil
		end

		return con
	end];

	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];

	tx_enter = txdo, tx_complete = txdone;

	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
		var [con] = [&lib.pq.PGconn](src.handle)
		if mode == lib.store.prepmode.full then [prep]
		elseif mode == lib.store.prepmode.conf or
		       mode == lib.store.prepmode.admin then 
			queries.conf_get.prep(con)
................................................................................
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.report('successfully wiped out everything parsav-related in database')
			return true
		else
			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end






















	end];

	conf_get = [terra(src: &lib.store.source, key: rawstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:String(0,0)
................................................................................
	): uint64
		var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 
		if r.sz == 0 then return 0 end
		defer r:free()
		var id = r:int(uint64,0,0)
		return id
	end];

	post_destroy = [terra(
		src: &lib.store.source,
		post: uint64
	): {}
		txdo(src)
			queries.post_destroy_prepare.exec(src, post)
			queries.post_destroy.exec(src, post)
		txdone(src)
	end];

	post_fetch = [terra(
		src: &lib.store.source,
		post: uint64
	): lib.mem.ptr(lib.store.post)
		var r = queries.post_fetch.exec(src, post)
		if r.sz == 0 then return [lib.mem.ptr(lib.store.post)].null() end

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

4
5
6
7
8
9
10

11
12
13
14
15
16
17
18







19
20
21
22
23
24
25
...
431
432
433
434
435
436
437

438
439
440
441
442
443
444
...
609
610
611
612
613
614
615
616
617
618

619



local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')

lib = {
	init = {}, util = util;
	load = function(lst)
		for _, l in pairs(lst) do

			local path = {}
			for m in l:gmatch('([^:]+)') do path[#path+1]=m end
			local tgt = lib
			for i=1,#path-1 do
				if tgt[path[i]] == nil then tgt[path[i]] = {} end
				tgt = tgt[path[i]]
			end
			tgt[path[#path]:gsub('-','_')] = terralib.loadfile(l:gsub(':','/') .. '.t')()







		end
	end;
	loadlib = function(name,hdr)
		local p = config.pkg[name]
		-- for _,v in pairs(p.dylibs) do
		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
		-- end
................................................................................
	'render:user-page';
	'render:timeline';

	'render:docpage';

	'render:conf:profile';
	'render:conf:sec';

	'render:conf';
	'route';
}

do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
................................................................................
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end

for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
local linkargs_d = linkargs -- controller is not multithreaded
if config.posix then
	linkargs_d[#linkargs_d+1] = '-pthread'
end
holler('linking with args',util.dump(linkargs))

terralib.saveobj('parsavd'..suffix, { main = entry_daemon }, linkargs_d, target)

terralib.saveobj('parsav' ..suffix, { main = lib.mgtool }, linkargs, target)










>







|
>
>
>
>
>
>
>







 







>







 







<

<
>

>
>
>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
...
618
619
620
621
622
623
624

625

626
627
628
629
630
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')

lib = {
	init = {}, util = util;
	load = function(lst)
		for _, l in pairs(lst) do
			io.stdout:write(' · processing module \27[1m' .. l ..'\27[m… ')
			local path = {}
			for m in l:gmatch('([^:]+)') do path[#path+1]=m end
			local tgt = lib
			for i=1,#path-1 do
				if tgt[path[i]] == nil then tgt[path[i]] = {} end
				tgt = tgt[path[i]]
			end
			local chunk = terralib.loadfile(l:gsub(':','/') .. '.t')
			if chunk ~= nil then
				tgt[path[#path]:gsub('-','_')] = chunk()
				print(' \27[1m[ \27[32mok\27[;1m ]\27[m')
			else
				print(' \27[1m[\27[31mfail\27[;1m]\27[m')
				os.exit(2)
			end
		end
	end;
	loadlib = function(name,hdr)
		local p = config.pkg[name]
		-- for _,v in pairs(p.dylibs) do
		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
		-- end
................................................................................
	'render:user-page';
	'render:timeline';

	'render:docpage';

	'render:conf:profile';
	'render:conf:sec';
	'render:conf:users';
	'render:conf';
	'route';
}

do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
................................................................................
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end

for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
local linkargs_d = linkargs -- controller is not multithreaded
if config.posix then
	linkargs_d[#linkargs_d+1] = '-pthread'
end



holler(' → linking \27[1mparsav\27[m with "' .. table.concat(linkargs,' ') .. '"')
terralib.saveobj('parsav' ..suffix, { main = lib.mgtool }, linkargs, target)

holler(' → linking \27[1mparsavd\27[m with "' .. table.concat(linkargs_d,' ') .. '"')
terralib.saveobj('parsavd'..suffix, { main = entry_daemon }, linkargs_d, target)

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





















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- 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 
render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
	if path.ct == 2 then
		var uid, ok = lib.math.shorthand.parse(path(1).ptr,path(1).ct)
		var user = co.srv:actor_fetch_uid(uid)
		if not user then goto e404 end
		var islinkct = false
		var cinp: lib.str.acc
		var clnk: lib.str.acc clnk:compose('<hr>')

		var cinpp = cinp:finalize() defer cinpp:free()
		var clnkp: pstr
		if islinkct then clnkp = clnk:finalize() else
			clnk:free()
			clnkp = pstr { ptr='', ct=0 }
		end
		var pg = data.view.conf_user_ctl {
			name = cs(user(0).handle);
			inputcontent = cinpp;
			linkcontent = clnkp;
		}
		var ret = pg:tostr()
		if islinkct then clnkp:free() end
		return ret
	else

	end
	do return pstr.null() end
	::e404:: co:complain(404, 'not found', 'there is no user or resource by that identifier on this server')

	do return pstr.null() end
end

return render_conf_users

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

175
176
177
178
179
180
181
182

183
184
185
186
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204



205
206

207







208
209




210

211




212
213
214
215
216

217
218
219
220
221
222
223
...
226
227
228
229
230
231
232



233


234
235
236
237
238
239
240
...
250
251
252
253
254
255
256



257
258
259
260
261
262
263
	if not post then
		co:complain(404, 'post not found', 'no such post is known to this server')
		return
	end
	defer post:free()

	if path.ct == 3 then
		if path(2):cmp(lib.str.lit 'edit') then

			if post(0).author ~= co.who.id then
				co:complain(403, 'forbidden', 'you cannot edit other people\'s posts')
				return
			end


			if meth == method.get then
				lib.render.compose(co, post.ptr, nil)
				return
			elseif meth == method.post then
				var newbody = co:postv('post')._0
				var newacl = co:postv('acl')._0
				var newsubj = co:postv('subject')._0
				if newbody ~= nil then post(0).body = newbody end
				if newacl  ~= nil then post(0).acl = newacl end
				if newsubj ~= nil then post(0).subject = newsubj end
				post(0):save(true)

				var lnk: lib.str.acc lnk:compose('/post/', path(1))
				co:reroute(lnk.buf)
				lnk:free()
			end
			return



		else goto badurl end
	end









	if meth == method.post then
		co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful')




		return

	end





	lib.render.tweet_page(co, path, post.ptr)
	do return end

	::badurl:: co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality')

end

terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
	var msg = pstring.null()
	if meth == method.post and path.ct >= 1 then
		var user_refresh = false var fail = false
		if path(1):cmp(lib.str.lit 'profile') then
................................................................................
			co.who.nym = co:postv('nym')._0
			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
			co.who.source:actor_save(co.who)
			msg = lib.str.plit 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually
		elseif path(1):cmp(lib.str.lit 'srv') then



		elseif path(1):cmp(lib.str.lit 'users') then


		elseif path(1):cmp(lib.str.lit 'sec') then
			var act = co:ppostv('act')
			if act:cmp(lib.str.plit 'invalidate') then
				lib.dbg('setting user\'s cookie validation time to now')
				co.who.source:auth_sigtime_user_alter(co.who.id, lib.osclock.time(nil))
				-- 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
				co:installkey('/conf/sec',co.aid)
................................................................................
		var go,golen = co:getv('go')
		if not fail and go ~= nil then
			co:reroute(go)
			return
		end
	end
	lib.render.conf(co,path,msg)



end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap







|
>
|
|
|
<
<
>











<
<
|
<


>
>
>
|
|
>
|
>
>
>
>
>
>
>
|
<
>
>
>
>
|
>
|
>
>
>
>




|
>







 







>
>
>

>
>







 







>
>
>







175
176
177
178
179
180
181
182
183
184
185
186


187
188
189
190
191
192
193
194
195
196
197
198


199

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
...
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
	if not post then
		co:complain(404, 'post not found', 'no such post is known to this server')
		return
	end
	defer post:free()

	if path.ct == 3 then
		var lnk: lib.str.acc lnk:compose('/post/', path(1))
		var lnkp = lnk:finalize() defer lnkp:free()
		if post(0).author ~= co.who.id then
			co:complain(403, 'forbidden', 'you cannot alter other people\'s posts')
			return


		elseif path(2):cmp(lib.str.lit 'edit') then
			if meth == method.get then
				lib.render.compose(co, post.ptr, nil)
				return
			elseif meth == method.post then
				var newbody = co:postv('post')._0
				var newacl = co:postv('acl')._0
				var newsubj = co:postv('subject')._0
				if newbody ~= nil then post(0).body = newbody end
				if newacl  ~= nil then post(0).acl = newacl end
				if newsubj ~= nil then post(0).subject = newsubj end
				post(0):save(true)


				co:reroute(lnkp.ptr)

			end
			return
		elseif path(2):cmp(lib.str.lit 'del') then
			if meth == method.get then
				var conf = data.view.confirm {
					title = lib.str.plit 'delete post';
					query = lib.str.plit 'are you sure you want to delete this post?';
					cancel = lnkp
				}
				var body = conf:tostr() defer body:free()
				co:stdpage([lib.srv.convo.page] {
					title = lib.str.plit 'post :: delete';
					class = lib.str.plit 'query';
					body = body; cache = false;
				})
				return
			elseif meth == method.post then

				var act = co:ppostv('act')
				if act:cmp(lib.str.plit 'confirm') then
					post(0).source:post_destroy(post(0).id)
					co:reroute('/') -- TODO maybe return to parent or conversation if possible
					return
				else goto badop end
			end
		else goto badurl end
	end

	if meth == method.post then goto badop end

	lib.render.tweet_page(co, path, post.ptr)
	do return end

	::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end
	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
end

terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
	var msg = pstring.null()
	if meth == method.post and path.ct >= 1 then
		var user_refresh = false var fail = false
		if path(1):cmp(lib.str.lit 'profile') then
................................................................................
			co.who.nym = co:postv('nym')._0
			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
			co.who.source:actor_save(co.who)
			msg = lib.str.plit 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually
		elseif path(1):cmp(lib.str.lit 'srv') then
			if not co.who.rights.powers.config() then goto nopriv end
		elseif path(1):cmp(lib.str.lit 'brand') then
			if not co.who.rights.powers.rebrand() then goto nopriv end
		elseif path(1):cmp(lib.str.lit 'users') then
			if not co.who.rights.powers:affect_users() then goto nopriv end

		elseif path(1):cmp(lib.str.lit 'sec') then
			var act = co:ppostv('act')
			if act:cmp(lib.str.plit 'invalidate') then
				lib.dbg('setting user\'s cookie validation time to now')
				co.who.source:auth_sigtime_user_alter(co.who.id, lib.osclock.time(nil))
				-- 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
				co:installkey('/conf/sec',co.aid)
................................................................................
		var go,golen = co:getv('go')
		if not fail and go ~= nil then
			co:reroute(go)
			return
		end
	end
	lib.render.conf(co,path,msg)
	do return end

	::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action')
end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap

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

68
69
70
71
72
73
74

75
76
77
78
79
80
81
...
339
340
341
342
343
344
345

346
347
348
349
350
351
352
...
522
523
524
525
526
527
528

529
530
531
532
533
534
535
...
606
607
608
609
610
611
612



613

614
615
616
617
618
619
620
	padding: 0.1in 0.2in;
	border: 1px solid black;
	color: tone(25%);
	text-shadow: 1px 1px black;
	text-decoration: none;
	text-align: center;
	cursor: default;

	background: linear-gradient(to bottom,
		tone(-47%),
		tone(-50%) 15%,
		tone(-50%) 75%,
		tone(-53%)
	);
	&:hover, &:focus {
................................................................................
.message {
	@extend %box;
	display: block;
	width: 4in;
	margin:auto;
	padding: 0.5in;
	text-align: center;

}

div.login {
	@extend %box;
	width: 4in;
	padding: 0.4in;
	> .msg {
................................................................................
		background: linear-gradient(to right, tone(-50%), transparent);
		margin-left: -0.4in;
		padding-left: 0.2in;
		text-shadow: 0 2px 0 black;
	}
}


body.conf main {
	display: grid;
	grid-template-columns: 2in 1fr;
	grid-template-rows: max-content 1fr;
	> menu {
		margin-left: -0.25in;
		grid-column: 1/2; grid-row: 1/2;
................................................................................
	}
	&.vertical-float {
		flex-flow: column;
		float: right;
		width: 40%;
		margin-left: 0.1in;
	}



	> %button { display: block; margin: 2px; flex-grow: 1 }

}

.check-panel {
	display: flex;
	flex-flow: row wrap;
	> label {
		display: block;







>







 







>







 







>







 







>
>
>
|
>







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
...
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
...
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
...
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
	padding: 0.1in 0.2in;
	border: 1px solid black;
	color: tone(25%);
	text-shadow: 1px 1px black;
	text-decoration: none;
	text-align: center;
	cursor: default;
	user-select: none;
	background: linear-gradient(to bottom,
		tone(-47%),
		tone(-50%) 15%,
		tone(-50%) 75%,
		tone(-53%)
	);
	&:hover, &:focus {
................................................................................
.message {
	@extend %box;
	display: block;
	width: 4in;
	margin:auto;
	padding: 0.5in;
	text-align: center;
	menu:first-of-type { margin-top: 0.3in; }
}

div.login {
	@extend %box;
	width: 4in;
	padding: 0.4in;
	> .msg {
................................................................................
		background: linear-gradient(to right, tone(-50%), transparent);
		margin-left: -0.4in;
		padding-left: 0.2in;
		text-shadow: 0 2px 0 black;
	}
}

menu { all: unset; display: block; }
body.conf main {
	display: grid;
	grid-template-columns: 2in 1fr;
	grid-template-rows: max-content 1fr;
	> menu {
		margin-left: -0.25in;
		grid-column: 1/2; grid-row: 1/2;
................................................................................
	}
	&.vertical-float {
		flex-flow: column;
		float: right;
		width: 40%;
		margin-left: 0.1in;
	}
	> %button {
		flex-basis: 0;
		flex-grow: 1;
		display: block; margin: 2px;
	}
}

.check-panel {
	display: flex;
	flex-flow: row wrap;
	> label {
		display: block;

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

335
336
337
338
339
340
341

342
343
344
345
346
347
348
			-- uid: uint64
	auth_sigtime_user_alter: {&m.source, uint64, m.timepoint} -> {}
			-- uid: uint64
			-- timestamp: timepoint

	post_save: {&m.source, &m.post} -> {}
	post_create: {&m.source, &m.post} -> uint64

	post_fetch: {&m.source, uint64} -> lib.mem.ptr(m.post)
	post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
	post_attach_ctl: {&m.source, uint64, uint64, bool} -> {}
		-- attaches or detaches an existing database artifact
			-- post id: uint64
			-- artifact id: uint64
			-- detach: bool







>







335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
			-- uid: uint64
	auth_sigtime_user_alter: {&m.source, uint64, m.timepoint} -> {}
			-- uid: uint64
			-- timestamp: timepoint

	post_save: {&m.source, &m.post} -> {}
	post_create: {&m.source, &m.post} -> uint64
	post_destroy: {&m.source, uint64} -> {}
	post_fetch: {&m.source, uint64} -> lib.mem.ptr(m.post)
	post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
	post_attach_ctl: {&m.source, uint64, uint64, bool} -> {}
		-- attaches or detaches an existing database artifact
			-- post id: uint64
			-- artifact id: uint64
			-- detach: bool

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



















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

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

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








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

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

13
14
15
16
17
18
19

20
21
22
23
24
25
26
	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';
	'conf-sec-credmg';

}

local ingest = function(filename)
	local hnd = io.open(path..'/'..filename)
	local txt = hnd:read('*a')
	io.close(hnd)
	txt = txt:gsub('([^\\])!%b[]', '%1')







>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';
	'conf-sec-credmg';
	'conf-user-ctl';
}

local ingest = function(filename)
	local hnd = io.open(path..'/'..filename)
	local txt = hnd:read('*a')
	io.close(hnd)
	txt = txt:gsub('([^\\])!%b[]', '%1')