| Comment: | continued iteration |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
0324d625467b68d51407a9ab6432c30e |
| User & Date: | lexi on 2020-12-30 00:43:11 |
| Other Links: | manifest | tags |
|
2020-12-30
| ||
| 02:44 | enable profile editing check-in: ac4a630ad5 user: lexi tags: trunk | |
| 00:43 | continued iteration check-in: 0324d62546 user: lexi tags: trunk | |
|
2020-12-29
| ||
| 15:48 | enable remote control of running instances check-in: f8816b0ab5 user: lexi tags: trunk | |
Modified backend/pgsql.t from [0f1425913d] to [30de1dd276].
131 131 select relatee as user from parsav_rels 132 132 where relator = $1::bigint and kind = <follow> 133 133 ), 134 134 followers as ( 135 135 select relator as user from parsav_rels 136 136 where relatee = $1::bigint and kind = <follow> 137 137 ), 138 - mutuals as (select * from follows intersect select * from followers) 138 + mutuals as ( 139 + select * from follows intersect select * from followers 140 + ) 139 141 140 - select count(tweets.*)::bigint, 141 - count(follows.*)::bigint, 142 - count(followers.*)::bigint, 143 - count(mutuals.*)::bigint 144 - from tweets, follows, followers, mutuals 142 + values ( 143 + (select count(tweets.*)::bigint from tweets), 144 + (select count(follows.*)::bigint from follows), 145 + (select count(followers.*)::bigint from followers), 146 + (select count(mutuals.*)::bigint from mutuals) 147 + ) 145 148 ]]):gsub('<(%w+)>',function(r) return tostring(lib.store.relation[r]) end) 146 149 }; 147 150 148 151 actor_auth_how = { 149 152 params = {rawstring, lib.store.inet}, sql = [[ 150 153 with mts as (select a.kind from parsav_auth as a 151 154 left join parsav_actors as u on u.id = a.uid ................................................................................ 278 281 (a.origin is null) 279 282 order by (p.posted, p.discovered) desc 280 283 limit case when $3::bigint = 0 then null 281 284 else $3::bigint end 282 285 offset $4::bigint 283 286 ]] 284 287 }; 288 + 289 + artifact_instantiate = { 290 + params = {binblob, binblob, pstring}, sql = [[ 291 + insert into parsav_artifacts (content,hash,mime) values ( 292 + $1::bytea, $2::bytea, $3::text 293 + ) on conflict do nothing returning id 294 + ]]; 295 + }; 296 + artifact_expropriate = { 297 + params = {uint64, uint64, pstring}, cmd = true, sql = [[ 298 + insert into parsav_artifact_claims (uid,rid,description,folder) values ( 299 + $1::bigint, $2::bigint, $3::text, 'new' 300 + ) on conflict do nothing 301 + ]]; 302 + }; 303 + artifact_quicksearch = { 304 + params = {binblob}, sql = [[ 305 + select id, (content is null) from parsav_artifacts where hash = $1::bytea 306 + limit 1 307 + ]]; 308 + }; 309 + artifact_disclaim = { 310 + params = {uint64, uint64}, cmd = true, sql = [[ 311 + delete from parsav_artifact_claims where 312 + uid = $1::bigint and 313 + rid = $2::bigint 314 + ]]; 315 + }; 316 + artifact_excise_forget = { 317 + -- delete the blasted thing and pretend it never existed 318 + params = {uint64}, cmd=true, sql = [[ 319 + delete from parsav_artifacts where id = $1::bigint 320 + ]]; 321 + }; 322 + artifact_excise_suppress_nullify = { 323 + -- banish the thing into the outer darkness, preventing 324 + -- it from ever being admitted into our databases, and 325 + -- tabulate a -- list of the degenerates who befouled 326 + -- their accounts with such wanton and execrable filth, 327 + -- the better to ensure their long-overdue punishment 328 + params = {uint64}, cmd=true, sql = [[ 329 + update parsav_artifacts 330 + set content = null 331 + where id = $1::bigint; 332 + ]]; 333 + }; 334 + artifact_excise_suppress_breaklinks = { 335 + -- "ERROR: cannot insert multiple commands into a prepared 336 + -- statement" are you fucking shitting me with this shit 337 + params = {uint64}, sql = [[ 338 + delete from parsav_artifact_claims where 339 + rid = $1::bigint 340 + returning uid, description, birth, folder; 341 + ]]; 342 + }; 343 + post_attach_ctl_ins = { 344 + params = {uint64, uint64}, cmd=true, sql = [[ 345 + update parsav_posts set 346 + artifacts = artifacts || $2::bigint 347 + where id = $1::bigint and not 348 + artifacts @> array[$2::bigint] 349 + ]]; 350 + }; 351 + post_attach_ctl_del = { 352 + params = {uint64, uint64}, cmd=true, sql = [[ 353 + update parsav_posts set 354 + artifacts = array_remove(artifacts, $2::bigint) 355 + where id = $1::bigint and 356 + artifacts @> array[$2::bigint] 357 + ]]; 358 + }; 285 359 } 286 - --($5::bool = false or p.parent is null) and 287 360 288 361 local struct pqr { 289 362 sz: intptr 290 363 res: &lib.pq.PGresult 291 364 } 292 365 terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end 293 366 terra pqr:null(row: intptr, col: intptr) ................................................................................ 685 758 lib.report('successfully wiped out everything parsav-related in database') 686 759 return true 687 760 else 688 761 lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res)) 689 762 return false 690 763 end 691 764 end]; 765 + 766 + tx_enter = [terra(src: &lib.store.source) 767 + var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin') 768 + if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then 769 + lib.dbg('beginning pgsql transaction') 770 + return true 771 + else 772 + lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res)) 773 + return false 774 + end 775 + end]; 776 + 777 + tx_complete = [terra(src: &lib.store.source) 778 + var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end') 779 + if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then 780 + lib.dbg('completing pgsql transaction') 781 + return true 782 + else 783 + lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res)) 784 + return false 785 + end 786 + end]; 692 787 693 788 conf_get = [terra(src: &lib.store.source, key: rawstring) 694 789 var r = queries.conf_get.exec(src, key) 695 790 if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else 696 791 defer r:free() 697 792 return r:String(0,0) 698 793 end ................................................................................ 905 1000 queries.auth_purge_type.exec(src, handle, uid, 'otp-%') 906 1001 end]; 907 1002 908 1003 auth_purge_trust = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} 909 1004 queries.auth_purge_type.exec(src, handle, uid, 'trust') 910 1005 end]; 911 1006 912 - actor_auth_register_uid = nil; -- not necessary for view-based auth 1007 + artifact_quicksearch = [terra( 1008 + src: &lib.store.source, 1009 + hash: binblob 1010 + ): {uint64, bool} 1011 + var srec = queries.artifact_quicksearch.exec(src, hash) 1012 + if srec.sz > 0 then 1013 + defer srec:free() 1014 + var id = srec:int(uint64,0,0) 1015 + var ban = srec:bool(0,1) 1016 + return id, ban 1017 + else return 0, false end 1018 + end]; 1019 + 1020 + artifact_instantiate = [terra( 1021 + src: &lib.store.source, 1022 + artifact: binblob, 1023 + mime: pstring 1024 + ): uint64 1025 + var arthash: uint8[lib.crypt.algsz.sha256] 1026 + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id), 1027 + artifact.ptr, artifact.ct, &arthash[0]) ~= 0 then 1028 + lib.bail('could not hash artifact to be instantiated') 1029 + end 1030 + var hashb = binblob{ptr=&arthash[0],ct=[arthash.type.N]} 1031 + 1032 + var srec = queries.artifact_quicksearch.exec(src, hashb) 1033 + if srec.sz > 0 then 1034 + defer srec:free() 1035 + var ban = srec:bool(0,1) 1036 + if ban then 1037 + lib.report('user attempted to instantiate forsaken artifact') 1038 + return 0 1039 + end 1040 + var oldid = srec:int(uint64,0,0) 1041 + return oldid 1042 + else -- not in db, insert 1043 + var nrec = queries.artifact_instantiate.exec(src, artifact, hashb, mime) 1044 + if nrec.sz == 0 then 1045 + lib.warn('failed to instantiate artifact -- are you running out of storage?') 1046 + return 0 1047 + else defer nrec:free() 1048 + var newid = nrec:int(uint64,0,0) 1049 + return newid 1050 + end 1051 + end 1052 + end]; 1053 + 1054 + post_attach_ctl = [terra( 1055 + src: &lib.store.source, 1056 + post: uint64, 1057 + artifact: uint64, 1058 + detach: bool 1059 + ): {} 1060 + if detach 1061 + then queries.post_attach_ctl_del.exec(src,post,artifact) 1062 + else queries.post_attach_ctl_ins.exec(src,post,artifact) 1063 + end 1064 + end]; 913 1065 1066 + actor_auth_register_uid = nil; -- TODO better support non-view based auth 914 1067 } 915 1068 916 1069 return b
Modified backend/schema/pgsql-drop.sql from [e1fb43be2e] to [17a37aa5f6].
5 5 drop table if exists parsav_actors cascade; 6 6 drop table if exists parsav_rights cascade; 7 7 drop table if exists parsav_posts cascade; 8 8 drop table if exists parsav_conversations cascade; 9 9 drop table if exists parsav_rels cascade; 10 10 drop table if exists parsav_acts cascade; 11 11 drop table if exists parsav_log cascade; 12 -drop table if exists parsav_attach cascade; 12 +drop table if exists parsav_artifacts cascade; 13 +drop table if exists parsav_artifact_claims cascade; 13 14 drop table if exists parsav_circles cascade; 14 15 drop table if exists parsav_rooms cascade; 15 16 drop table if exists parsav_room_members cascade; 16 17 drop table if exists parsav_invites cascade; 17 -drop table if exists parsav_interventions cascade; 18 +drop table if exists parsav_sanctions cascade; 18 19 drop table if exists parsav_auth cascade;
Modified backend/schema/pgsql.sql from [0ef43163b5] to [cb277a93b1].
26 26 id bigint primary key default (1+random()*(2^63-1))::bigint, 27 27 nym text, 28 28 handle text not null, -- nym [@handle@origin] 29 29 origin bigint references parsav_servers(id) 30 30 on delete cascade, -- null origin = local actor 31 31 knownsince timestamp, 32 32 bio text, 33 + avatarid bigint, -- artifact id, null if remote 33 34 avataruri text, -- null if local 34 35 rank smallint not null default 0, 35 36 quota integer not null default 1000, 36 37 key bytea, -- private if localactor; public if remote 37 38 epithet text, 38 39 authtime timestamp not null default now(), -- cookies earlier than this timepoint will not be accepted 39 40 ................................................................................ 55 56 author bigint references parsav_actors(id) 56 57 on delete cascade, 57 58 subject text, 58 59 acl text not null default 'all', -- just store the script raw 🤷 59 60 body text, 60 61 posted timestamp not null, 61 62 discovered timestamp not null, 62 - parent bigint not null default 0, 63 + parent bigint not null default 0, -- if post: part of conversation; if chatroom: top-level post 63 64 circles bigint[], -- TODO at edit or creation, iterate through each circle 64 65 mentions bigint[], -- a user has, check if it can see her post, and if so add 66 + artifacts bigint[], 65 67 66 68 convoheaduri text 67 69 -- only used for tracking foreign conversations and tying them to post heads; 68 70 -- local conversations are tracked directly and mapped to URIs based on the 69 71 -- head's ID. null if native tweet or not the first tweet in convo 70 72 ); 71 73 ................................................................................ 93 95 id bigint primary key default (1+random()*(2^63-1))::bigint, 94 96 time timestamp not null default now(), 95 97 actor bigint references parsav_actors(id) 96 98 on delete cascade, 97 99 post bigint not null 98 100 ); 99 101 100 -create table parsav_attach ( 102 +create table parsav_artifacts ( 101 103 id bigint primary key default (1+random()*(2^63-1))::bigint, 102 104 birth timestamp not null default now(), 103 - content bytea not null, 104 - mime text, -- null if unknown, will be reported as x-octet-stream 105 + content bytea, -- if null, this is a "ban record" preventing content matching the hash from being re-uploaded 106 + hash bytea unique not null, -- sha256 hash of content 107 + -- it would be cool to use a computed column for this, but i don't want 108 + -- to lock people into PG12 or drag in the pgcrypto extension just for this 109 + mime text -- null if unknown, will be reported as x-octet-stream 110 +); 111 +create index on parsav_artifacts (mime); 112 + 113 +create table parsav_artifact_claims ( 114 + birth timestamp not null default now(), 115 + uid bigint references parsav_actors(id) on delete cascade, 116 + rid bigint references parsav_artifacts(id) on delete cascade, 105 117 description text, 106 - parent bigint -- post id, or userid for avatars 118 + folder text, 119 + 120 + unique (uid,rid) 107 121 ); 122 +create index on parsav_artifact_claims (uid); 108 123 109 124 create table parsav_circles ( 110 125 id bigint primary key default (1+random()*(2^63-1))::bigint, 111 - owner bigint not null references parsav_actors(id), 126 + owner bigint not null references parsav_actors(id) on delete cascade, 112 127 name text not null, 113 128 members bigint[] not null default array[]::bigint[], 114 129 115 130 unique (owner,name) 116 131 ); 117 132 118 133 create table parsav_rooms ( 119 134 id bigint primary key default (1+random()*(2^63-1))::bigint, 120 - origin bigint references parsav_servers(id), 135 + origin bigint references parsav_servers(id) on delete cascade, 121 136 name text not null, 122 137 description text not null, 123 138 policy smallint not null 124 139 ); 125 140 126 141 create table parsav_room_members ( 127 - room bigint references parsav_rooms(id), 128 - member bigint references parsav_actors(id), 142 + room bigint not null references parsav_rooms(id) on delete cascade, 143 + member bigint not null references parsav_actors(id) on delete cascade, 129 144 rank smallint not null default 0, 130 145 admin boolean not null default false, -- non-admins with rank can only moderate + invite 131 146 title text, -- admin-granted title like reddit flair 132 147 vouchedby bigint references parsav_actors(id) 133 148 ); 134 149 135 150 create table parsav_invites ( 136 151 id bigint primary key default (1+random()*(2^63-1))::bigint, 137 152 -- when a user is created from an invite, the invite is deleted and the invite 138 153 -- ID becomes the user ID. privileges granted on the invite ID during the invite 139 154 -- process are thus inherited by the user 140 - issuer bigint references parsav_actors(id), 155 + issuer bigint references parsav_actors(id) on delete set null, 141 156 handle text, -- admin can lock invite to specific handle 142 157 rank smallint not null default 0, 143 158 quota integer not null default 1000 144 159 ); 145 160 146 -create table parsav_interventions ( 161 +create table parsav_sanctions ( 147 162 id bigint primary key default (1+random()*(2^63-1))::bigint, 148 - issuer bigint references parsav_actors(id) not null, 163 + issuer bigint references parsav_actors(id) on delete set null, 149 164 scope bigint, -- can be null or room for local actions 150 - nature smallint not null, -- silence, suspend, disemvowel, etc 151 - victim bigint not null, -- could potentially target group as well 152 - expire timestamp -- auto-expires if set 165 + nature smallint not null, -- silence, suspend, disemvowel, censor, noreply, etc 166 + victim bigint not null, -- can be user, room, or post 167 + expire timestamp, -- auto-expires if set 168 + review timestamp, -- brings up for review at given time if set 169 + reason text, -- visible to victim if set 170 + context text -- admin-only note 153 171 ); 154 172 155 173 -- create a temporary managed auth table; we can delete this later 156 174 -- if it ends up being replaced with a view 157 175 %include pgsql-auth.sql%
Modified mgtool.t from [c623f2c8c5] to [03b0c72484].
67 67 local fn = f.field or f[1] 68 68 local ft = f.type or f[2] 69 69 if fn == meth then rt = ft.type.returntype break end 70 70 end 71 71 72 72 return quote 73 73 var r: rt 74 - if self.all 74 + if self.all or (self.srv ~= nil and self.srv.sources.ct == 1) 75 75 then r=self.srv:[meth]([expr]) 76 76 elseif self.src ~= nil then r=self.src:[meth]([expr]) 77 77 else lib.bail('no data source specified') 78 78 end 79 79 in r end 80 80 end) 81 81 ................................................................................ 271 271 return 1 272 272 end 273 273 if dbmode.arglist.ct < 1 then goto cmderr end 274 274 275 275 srv:setup(cnf) 276 276 if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then 277 277 lib.report('initializing new database structure for domain ', dbmode.arglist(1)) 278 - dlg:dbsetup() 279 - srv:conprep(lib.store.prepmode.conf) 280 - dlg:conf_set('instance-name', dbmode.arglist(1)) 281 - do var sec: int8[65] gensec(&sec[0]) 282 - dlg:conf_set('server-secret', &sec[0]) 283 - end 284 - lib.report('database setup complete; use mkroot to create an administrative user') 278 + if dlg:dbsetup() then 279 + srv:conprep(lib.store.prepmode.conf) 280 + dlg:conf_set('instance-name', dbmode.arglist(1)) 281 + do var sec: int8[65] gensec(&sec[0]) 282 + dlg:conf_set('server-secret', &sec[0]) 283 + end 284 + lib.report('database setup complete; use mkroot to create an administrative user') 285 + else lib.bail('initialization process interrupted') end 285 286 elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then 286 287 var confirmstrs = array( 287 - 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa' 288 + 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa', 289 + 'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst' 288 290 ) 289 291 var cfmstr: int8[64] cfmstr[0] = 0 290 292 var tdx = lib.osclock.time(nil) / 60 291 293 for i=0,3 do 292 294 if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end 293 295 lib.str.cat(&cfmstr[0], confirmstrs[(tdx ^ (173*i)) % [confirmstrs.type.N]]) 294 296 end
Modified render/conf.t from [6e08f785f6] to [79b6da76d7].
62 62 panel = panel; 63 63 } 64 64 65 65 var pgt = pg:tostr() defer pgt:free() 66 66 co:stdpage([lib.srv.convo.page] { 67 67 title = 'configure'; body = pgt; 68 68 class = lib.str.plit 'conf'; 69 + cache = false; 69 70 }) 70 71 71 72 if panel.ct ~= 0 then panel:free() end 72 73 end 73 74 74 75 return render_conf
Modified render/docpage.t from [7e1168b387] to [3eda6110a6].
44 44 parent = par; 45 45 priv = restrict; 46 46 title = R(t.meta.title); 47 47 content = page { 48 48 title = ['documentation :: ' .. t.meta.title]; 49 49 body = [ t.text ]; 50 50 class = P'doc article'; 51 + cache = true; 51 52 }; 52 53 } end 53 54 end 54 55 55 56 local terra 56 57 showpage(co: &lib.srv.convo, id: pref) 57 58 var [pages] = array([allpages]) ................................................................................ 107 108 list:lpush('</ul>') 108 109 109 110 var bp = list:finalize() 110 111 co:stdpage(page { 111 112 title = 'documentation'; 112 113 body = bp; 113 114 class = P'doc listing'; 115 + cache = false; 114 116 }) 115 117 bp:free() 116 118 else showpage(co, pg) end 117 119 end 118 120 119 121 return render_docpage
Modified render/login.t from [dd5c50c3e9] to [7ea4ccf2b4].
1 1 -- vim: ft=terra 2 2 local pstr = lib.mem.ptr(int8) 3 3 local P = lib.str.plit 4 4 local terra 5 5 login_form(co: &lib.srv.convo, user: &lib.store.actor, creds: &lib.store.credset, msg: pstr) 6 - var doc = data.view.docskel { 7 - instance = co.srv.cfg.instance; 6 + var doc = [lib.srv.convo.page] { 8 7 title = lib.str.plit 'instance logon'; 9 8 class = lib.str.plit 'login'; 10 - navlinks = co.navbar; 9 + cache = false; 11 10 } 12 11 13 12 if user == nil then 14 13 var form = data.view.login_username { 15 14 loginmsg = msg; 16 15 } 17 16 if form.loginmsg.ptr == nil then ................................................................................ 52 51 end 53 52 54 53 doc.body = ch:tostr() 55 54 else 56 55 -- pick a method 57 56 end 58 57 59 - var hdrs = array( 60 - lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 61 - ) 62 - doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 58 + co:stdpage(doc) 63 59 doc.body:free() 64 60 end 65 61 66 62 return login_form
Modified render/nav.t from [27572ae99b] to [4a737bb7ff].
1 1 -- vim: ft=terra 2 2 local terra 3 3 render_nav(co: &lib.srv.convo) 4 4 var t: lib.str.acc t:init(64) 5 5 if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then 6 - t:lpush('<a href="/">timeline</a>') 6 + t:lpush(' <a href="/">timeline</a>') 7 7 end 8 8 if co.who ~= nil then 9 - t:lpush('<a href="/compose">compose</a> <a href="/'):push(co.who.xid,0) 9 + t:lpush(' <a href="/compose">compose</a> <a href="/'):push(co.who.xid,0) 10 10 t:lpush('">profile</a> <a href="/conf">configure</a> <a href="/doc">docs</a> <a href="/logout">log out</a>') 11 11 else 12 - t:lpush('<a href="/doc">docs</a> <a href="/login">log in</a>') 12 + t:lpush(' <a href="/doc">docs</a> <a href="/login">log in</a>') 13 13 end 14 14 return t:finalize() 15 15 end 16 16 return render_nav
Modified render/nym.t from [89e574dd98] to [0d2437aadd].
1 1 -- vim: ft=terra 2 2 local pstr = lib.mem.ptr(int8) 3 +local terra cs(s: rawstring) 4 + return pstr { ptr = s, ct = lib.str.sz(s) } 5 +end 3 6 4 7 local terra 5 8 render_nym(who: &lib.store.actor, scope: uint64) 6 9 var n: lib.str.acc n:init(128) 10 + var xidsan = lib.html.sanitize(cs(who.xid),false) 7 11 if who.nym ~= nil and who.nym[0] ~= 0 then 8 - n:compose('<span class="nym">',who.nym,'</span> [<span class="handle">', 9 - who.xid,'</span>]') 10 - else n:compose('<span class="handle">',who.xid,'</span>') end 12 + var nymsan = lib.html.sanitize(cs(who.nym),false) 13 + n:compose('<span class="nym">',nymsan,'</span> [<span class="handle">', 14 + xidsan,'</span>]') 15 + nymsan:free() 16 + else n:compose('<span class="handle">',xidsan,'</span>') end 17 + xidsan:free() 11 18 12 19 if who.epithet ~= nil then 13 - n:lpush(' <span class="epithet">'):push(who.epithet,0):lpush('</span>') 20 + var episan = lib.html.sanitize(cs(who.epithet),false) 21 + n:lpush(' <span class="epithet">'):ppush(episan):lpush('</span>') 22 + episan:free() 14 23 end 15 24 16 25 -- TODO: if scope == chat room then lookup titles in room member db 17 - 18 26 return n:finalize() 19 27 end 20 28 21 29 return render_nym
Modified render/timeline.t from [873aeea861] to [2afba48373].
29 29 var acc: lib.str.acc acc:init(1024) 30 30 for i = 0, posts.sz do 31 31 lib.render.tweet(co, posts(i).ptr, &acc) 32 32 posts(i):free() 33 33 end 34 34 posts:free() 35 35 36 - var doc = data.view.docskel { 37 - instance = co.srv.cfg.instance; 36 + var doc = [lib.srv.convo.page] { 38 37 title = lib.str.plit'timeline'; 39 38 body = acc:finalize(); 40 39 class = lib.str.plit'timeline'; 41 - navlinks = co.navbar; 40 + cache = false; 42 41 } 43 - var hdrs = array( 44 - lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 45 - ) 46 - doc:send(co.con,200,[lib.mem.ptr(lib.http.header)] {ct = 1, ptr = &hdrs[0]}) 42 + co:stdpage(doc) 47 43 doc.body:free() 48 44 end 49 45 return render_timeline
Modified render/userpage.t from [edce0da550] to [8e478d7a95].
26 26 end 27 27 posts:free() 28 28 29 29 var bdf = acc:finalize() 30 30 co:stdpage([lib.srv.convo.page] { 31 31 title = tiptr; body = bdf; 32 32 class = lib.str.plit 'profile'; 33 + cache = false; 33 34 }) 34 35 35 36 tiptr:free() 36 37 bdf:free() 37 38 end 38 39 39 40 return render_userpage
Modified route.t from [4ebb6db558] to [5e16c3f22b].
115 115 if aid == 0 then 116 116 lib.render.login(co, nil, nil, lib.str.plit 'authentication failure') 117 117 else 118 118 var sesskey: int8[lib.session.maxlen + #lib.session.cookiename + #"=; Path=/" + 1] 119 119 do var p = &sesskey[0] 120 120 p = lib.str.ncpy(p, [lib.session.cookiename .. '='], [#lib.session.cookiename + 1]) 121 121 p = p + lib.session.cookie_gen(co.srv.cfg.secret, aid, lib.osclock.time(nil), p) 122 - lib.dbg('sending cookie',&sesskey[0]) 122 + lib.dbg('sending cookie ',{&sesskey[0],15}) 123 123 p = lib.str.ncpy(p, '; Path=/', 9) 124 124 end 125 125 co:reroute_cookie('/', &sesskey[0]) 126 126 end 127 127 end 128 128 if act.ptr ~= nil and fakeact == false then act:free() end 129 129 else
Modified srv.t from [7727a773dd] to [4721bcd333].
156 156 ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0) 157 157 }) 158 158 end 159 159 160 160 terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end 161 161 162 162 terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 163 - var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }) 163 + var hdrs = array( 164 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 165 + lib.http.header { key = 'Cache-Control', value = 'no-store' } 166 + ) 164 167 165 168 var ti: lib.str.acc ti:compose('error :: ', title) 166 169 var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>',title,'</h1><p>',msg,'</p></div>') 167 170 var body = data.view.docskel { 168 171 instance = self.srv.cfg.instance; 169 172 title = ti:finalize(); 170 173 body = bo:finalize(); ................................................................................ 194 197 in ok end 195 198 end) 196 199 197 200 struct convo.page { 198 201 title: pstring 199 202 body: pstring 200 203 class: pstring 204 + cache: bool 201 205 } 202 206 203 207 terra convo:stdpage(pg: convo.page) 204 208 var doc = data.view.docskel { 205 209 instance = self.srv.cfg.instance; 206 210 title = pg.title; 207 211 body = pg.body; 208 212 class = pg.class; 209 213 navlinks = self.navbar; 210 214 } 211 215 212 216 var hdrs = array( 213 - lib.http.header { 'Content-Type', 'text/html; charset=UTF-8' } 217 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 218 + lib.http.header { key = 'Cache-Control', value = 'no-store' } 214 219 ) 215 220 216 - doc:send(self.con,200,[lib.mem.ptr(lib.http.header)] {ct = [hdrs.type.N], ptr = &hdrs[0]}) 221 + doc:send(self.con,200,[lib.mem.ptr(lib.http.header)] {ct = [hdrs.type.N] - lib.trn(pg.cache,1,0), ptr = &hdrs[0]}) 217 222 end 218 223 219 224 -- CALL ONLY ONCE PER VAR 220 225 terra convo:postv(name: rawstring) 221 226 if self.varbuf.ptr == nil then 222 227 self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) 223 228 self.vbofs = self.varbuf.ptr
Modified static/style.scss from [1f479ddac7] to [a08d589c48].
127 127 tone(-50%), 128 128 tone(-35%) 129 129 ); 130 130 131 131 input[type='text'], input[type='password'], textarea { 132 132 @extend %serif; 133 133 padding: 0.08in 0.1in; 134 + box-sizing: border-box; 134 135 border: 1px solid black; 135 136 background: linear-gradient(to bottom, tone(-55%), tone(-40%)); 136 137 font-size: 16pt; 137 138 color: tone(25%); 138 139 box-shadow: inset 0 0 20px -3px tone(-55%); 139 140 &:focus { 140 141 color: white; ................................................................................ 362 363 > a { @extend %button; grid-column: 1 / 2; grid-row: 3/4; } 363 364 } 364 365 } 365 366 366 367 form.compose { 367 368 @extend %box; 368 369 display: grid; 369 - grid-template-columns: 1.1in 2fr min-content 1fr; 370 + grid-template-columns: 1.1in 2fr min-content 1fr 1.5fr; 370 371 grid-template-rows: 1fr min-content; 371 372 grid-gap: 2px; 372 373 padding: 0.1in; 373 374 > img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;} 374 375 > textarea { 375 - grid-column: 2/5; grid-row: 1/2; height: 3in; 376 + grid-column: 2/6; grid-row: 1/2; height: 3in; 376 377 resize: vertical; 377 378 margin-bottom: 0.08in; 378 379 } 379 380 > input[name="acl"] { grid-column: 2/3; grid-row: 2/3; } 380 - > button { grid-column: 4/5; grid-row: 2/3; } 381 + > button[value="post"] { grid-column: 5/6; grid-row: 2/3; } 382 + > button[value="attach"] { grid-column: 4/5; grid-row: 2/3; } 381 383 a.help[href] { margin-right: 0.05in } 382 384 } 383 385 384 386 a.help[href] { 385 387 display: block; 386 388 text-align: center; 387 389 padding: 0.09in 0.2in; ................................................................................ 541 543 padding-top: 0.12in; 542 544 background: linear-gradient(to right, tone(-50%), tone(-50%,-0.7)); 543 545 border: 1px solid tone(-55%); 544 546 border-left: none; 545 547 text-shadow: 1px 1px 0 black; 546 548 } 547 549 } 550 + 551 +} 552 + 553 +form { 554 + .elem { 555 + margin: 0.1in 0; 556 + label { display:block; font-weight: bold; padding: 0.03in 0; } 557 + .txtbox { 558 + @extend %serif; 559 + box-sizing: border-box; 560 + padding: 0.08in 0.1in; 561 + border: 1px solid black; 562 + background: tone(-55%); 563 + } 564 + input, textarea, .txtbox { 565 + display: block; 566 + width: 100%; 567 + } 568 + button { float: right; width: 50%; } 569 + } 548 570 }
Modified store.t from [763fd9ba8a] to [69369cc5c2].
12 12 relation = lib.enum { 13 13 'follow', 'mute', 'block' 14 14 }; 15 15 credset = lib.set { 16 16 'pw', 'otp', 'challenge', 'trust' 17 17 }; 18 18 privset = lib.set { 19 - 'post', 'edit', 'acct', 'upload', 'censor', 'admin' 19 + 'post', 'edit', 'acct', 'upload', 'censor', 'admin', 'invite' 20 20 }; 21 21 powerset = lib.set { 22 22 -- user powers -- default on 23 23 'login', 'visible', 'post', 'shout', 24 24 'propagate', 'upload', 'acct', 'edit'; 25 25 26 26 -- admin powers -- default off 27 27 'purge', 'config', 'censor', 'suspend', 28 28 'cred', 'elevate', 'demote', 'rebrand', -- modify site's brand identity 29 - 'herald' -- grant serverwide epithets 29 + 'herald', -- grant serverwide epithets 30 + 'invite' -- *unlimited* invites 30 31 }; 31 32 prepmode = lib.enum { 32 33 'full','conf','admin' 33 34 } 34 35 } 35 36 36 37 m.privmap = {} ................................................................................ 52 53 53 54 struct m.source 54 55 55 56 struct m.rights { 56 57 rank: uint16 -- lower = more powerful except 0 = regular user 57 58 -- creating staff automatically assigns rank immediately below you 58 59 quota: uint32 -- # of allowed tweets per day; 0 = no limit 60 + invites: intptr -- # of people left this user can invite 59 61 60 62 powers: m.powerset 61 63 } 62 64 63 65 terra m.rights_default() 64 - var pow: m.powerset pow:fill() 65 - (pow.purge << false) 66 - (pow.config << false) 67 - (pow.censor << false) 68 - (pow.suspend << false) 69 - (pow.elevate << false) 70 - (pow.demote << false) 71 - (pow.cred << false) 72 - (pow.rebrand << false) 73 - return m.rights { rank = 0, quota = 1000, powers = pow; } 66 + var pow: m.powerset pow:clear() 67 + (pow.login << true) 68 + (pow.visible << true) 69 + (pow.post << true) 70 + (pow.shout << true) 71 + (pow.propagate << true) 72 + (pow.upload << true) 73 + (pow.acct << true) 74 + (pow.edit << true) 75 + return m.rights { rank = 0, quota = 1000, invites = 0, powers = pow; } 74 76 end 75 77 76 78 struct m.actor { 77 79 id: uint64 78 80 nym: str 79 81 handle: str 80 82 origin: uint64 ................................................................................ 191 193 var bytes = bits / 8 192 194 var hexchs = bytes * 2 193 195 var segs = hexchs / 4 194 196 var seps = segs - 1 195 197 var maxsz = hexchs + seps + 1 196 198 else return nil end 197 199 end 200 + 201 +struct m.kompromat { 202 +-- The Evidence 203 + id: uint64 204 + perp: uint64 -- whodunnit 205 + desc: str 206 + post: uint64 -- the post in question, if any 207 + reporter: uint64 -- 0 = originated automatically by the System itself 208 + resolution: str -- null for unresolved 209 + -- as proto: set resolution to empty string to search for resolved incidents 210 +} 211 + 212 +struct m.sanction { 213 + id: uint64 214 + issuer: uint64 215 + scope: uint64 216 + nature: uint16 217 + victim: uint64 218 + autoexpire: bool expire: m.timepoint 219 + timedreview: bool review: m.timepoint 220 + reason: str 221 + context: str 222 +} 198 223 199 224 struct m.auth { 225 +-- a credential record 200 226 aid: uint64 201 227 uid: uint64 202 228 aname: str 203 229 netmask: m.inet 204 230 privs: m.privset 205 231 blacklist: bool 206 232 } ................................................................................ 209 235 struct m.backend { id: rawstring 210 236 open: &m.source -> &opaque 211 237 close: &m.source -> {} 212 238 dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`) 213 239 conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place 214 240 obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database 215 241 242 + tx_enter: &m.source -> bool 243 + tx_complete: &m.source -> bool 244 + -- these two functions are special, in that they should be called 245 + -- directly on a specific backend, rather than passed down to the 246 + -- backends by the server; that is pathological behavior that will 247 + -- not have the desired effect 248 + 216 249 conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) 217 250 conf_set: {&m.source, rawstring, rawstring} -> {} 218 251 conf_reset: {&m.source, rawstring} -> {} 219 252 220 253 actor_create: {&m.source, &m.actor} -> uint64 221 254 actor_save_privs: {&m.source, &m.actor} -> {} 222 255 actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) ................................................................................ 273 306 auth_purge_pw: {&m.source, uint64, rawstring} -> {} 274 307 auth_purge_otp: {&m.source, uint64, rawstring} -> {} 275 308 auth_purge_trust: {&m.source, uint64, rawstring} -> {} 276 309 277 310 post_save: {&m.source, &m.post} -> {} 278 311 post_create: {&m.source, &m.post} -> uint64 279 312 post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 313 + post_attach_ctl: {&m.source, uint64, uint64, bool} -> {} 314 + -- attaches or detaches an existing database artifact 315 + -- post id: uint64 316 + -- artifact id: uint64 317 + -- detach: bool 318 + artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 319 + -- instantiate an artifact in the database, either installing a new 320 + -- artifact or returning the id of an existing artifact with the same hash 321 + -- artifact: bytea 322 + -- mime: pstring 323 + artifact_quicksearch: {&m.source, lib.mem.ptr(uint8)} -> {uint64,bool} 324 + -- checks whether a hash is already in the database without uploading 325 + -- the entire file to the database server 326 + -- hash: bytea 327 + --> artifact id (0 if null), suppressed? 328 + artifact_expropriate: {&m.source, uint64, uint64, lib.mem.ptr(int8)} -> {} 329 + -- claims an existing artifact for the user's own collection 330 + -- uid: uint64 331 + -- artifact id: uint64 332 + -- description: pstring 333 + artifact_disclaim: {&m.source, uint64, uint64} -> {} 334 + -- a user disclaims their ownership stake in an artifact, removing it from 335 + -- the database entirely if they were the only owner, and removing their 336 + -- description of it either way 337 + -- uid: uint64 338 + -- artifact id: uint64 339 + artifact_excise: {&m.source, uint64, bool} -> {} 340 + -- (admin action) forcibly excise an artifact from the database, deleting 341 + -- all links to it and removing it from users' collections. if "blacklist," 342 + -- the artifact will be banned and attempts to upload it in the future 343 + -- will fail, triggering a report. mainly intended for dealing with spam, 344 + -- IP violations, That Which Shall Not Be Named, and various other infohazards. 345 + -- artifact id: uint64 346 + -- blacklist: bool 347 + 348 + nkvd_report_issue: {&m.source, &m.kompromat} -> {} 349 + -- an incidence of Badthink has been detected. report it immediately 350 + -- to the Supreme Soviet 351 + nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat) 352 + -- search through the Archives 353 + -- proto: kompromat (null for all records, or a prototype describing the records to return) 354 + nkvd_sanction_issue: {&m.source, &m.sanction} -> uint64 355 + nkvd_sanction_vacate: {&m.source, uint64} -> {} 356 + nkvd_sanction_enum_target: {&m.source, uint64} -> {} 357 + nkvd_sanction_enum_issuer: {&m.source, uint64} -> {} 358 + nkvd_sanction_review: {&m.source, m.timepoint} -> {} 359 + 280 360 convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post) 281 - convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post) 361 + convo_fetch_cid: {&m.source,uint64} -> lib.mem.ptr(m.post) 282 362 283 363 timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 284 364 timeline_instance_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 285 365 } 286 366 287 367 struct m.source { 288 368 backend: &m.backend
Modified view/compose.tpl from [5ccb8d92d6] to [a602850007].
1 1 <form class="compose" method="post"> 2 2 <img src="/avi/@handle"> 3 3 <textarea autofocus name="post" placeholder="it was a dark and stormy night…">@!content</textarea> 4 4 <input required autocomplete="on" type="text" name="acl" class="acl" value="@acl" list="scopes" placeholder="access control"> @?acl 5 - <button type="submit">commit</button> 5 + <button type="submit" name="act" value="post">commit</button> 6 + <button type="submit" name="act" value="attach">attach</button> 6 7 </form> 7 8 8 9 <datalist id="scopes"> 9 10 <option>all</option> 10 11 <option>mentioned</option> 11 12 <option>local</option> 12 13 <option>mutual</option>
Modified view/conf-profile.tpl from [746111dd26] to [f1f77d7014].
1 1 <form method="post"> 2 - <label>handle <div class="txtbox">@!handle</div></label> 3 - <label>display name <input type="text" name="nym" value="@:nym"></label> 4 - <label>bio <textarea name="bio">@!bio</textarea></label> 5 - <input type="submit" value="commit"> 2 + <div class="elem"><label>handle</label> <div class="txtbox">@!handle</div> 3 + <div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div> 4 + <div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div> 5 + <button>commit</button> 6 6 </form>