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: |
db4c5fd644227803a989a7a70a842129 |
| 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')