Index: backend/pgsql.t ================================================================== --- backend/pgsql.t +++ backend/pgsql.t @@ -302,10 +302,24 @@ 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, @@ -775,10 +789,33 @@ 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) @@ -805,10 +842,12 @@ 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 @@ -839,32 +878,10 @@ 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 @@ -1016,10 +1033,20 @@ 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) Index: parsav.t ================================================================== --- parsav.t +++ parsav.t @@ -6,18 +6,26 @@ 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 - tgt[path[#path]:gsub('-','_')] = terralib.loadfile(l:gsub(':','/') .. '.t')() + 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 @@ -433,10 +441,11 @@ 'render:docpage'; 'render:conf:profile'; 'render:conf:sec'; + 'render:conf:users'; 'render:conf'; 'route'; } do @@ -611,9 +620,11 @@ 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) +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 Index: render/conf/users.t ================================================================== --- render/conf/users.t +++ render/conf/users.t @@ -0,0 +1,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('
') + + 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 Index: route.t ================================================================== --- route.t +++ route.t @@ -177,16 +177,16 @@ 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 - + 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 @@ -194,28 +194,45 @@ 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() + 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 - co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') - return - end + if meth == method.post then goto badop 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') + ::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 @@ -228,11 +245,16 @@ 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)) @@ -252,10 +274,13 @@ 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) Index: static/style.scss ================================================================== --- static/style.scss +++ static/style.scss @@ -70,10 +70,11 @@ 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%) @@ -341,10 +342,11 @@ 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; @@ -524,10 +526,11 @@ 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 { @@ -608,11 +611,15 @@ flex-flow: column; float: right; width: 40%; margin-left: 0.1in; } - > %button { display: block; margin: 2px; flex-grow: 1 } + > %button { + flex-basis: 0; + flex-grow: 1; + display: block; margin: 2px; + } } .check-panel { display: flex; flex-flow: row wrap; Index: store.t ================================================================== --- store.t +++ store.t @@ -337,10 +337,11 @@ -- 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 ADDED view/conf-user-ctl.tpl Index: view/conf-user-ctl.tpl ================================================================== --- view/conf-user-ctl.tpl +++ view/conf-user-ctl.tpl @@ -0,0 +1,9 @@ +
+
+ +
@name
+
+ @inputcontent + + @linkcontent +
Index: view/confirm.tpl ================================================================== --- view/confirm.tpl +++ view/confirm.tpl @@ -1,9 +1,9 @@ -
+

@title

@query

cancel
Index: view/load.lua ================================================================== --- view/load.lua +++ view/load.lua @@ -15,10 +15,11 @@ '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')