| Comment: | work on admin ui |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
7129658e1d31700f1ac316f1d4d2962d |
| User & Date: | lexi on 2021-01-02 04:47:03 |
| Other Links: | manifest | tags |
|
2021-01-02
| ||
| 18:32 | iterate on user mgmt UI check-in: f09cd18161 user: lexi tags: trunk | |
| 04:47 | work on admin ui check-in: 7129658e1d user: lexi tags: trunk | |
|
2021-01-01
| ||
| 16:42 | handle (some) deletions in live.js check-in: 53ef86f7ff user: lexi tags: trunk | |
Modified backend/pgsql.t from [30375d8380] to [962a3e64e0].
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 .. 42 43 44 45 46 47 48 49 50 51 52 53 54 55 .. 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 ... 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 ... 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 ... 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 ... 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 .... 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 .... 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 .... 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 |
actor_fetch_uid = {
params = {uint64}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors as a
left join parsav_servers as s
on a.origin = s.id
where a.id = $1::bigint
]];
};
................................................................................
actor_fetch_xid = {
params = {pstring}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
coalesce(s.domain,
(select value from parsav_config
where key='domain' limit 1)) as domain
from parsav_actors as a
left join parsav_servers as s
................................................................................
where $1::text = (a.handle || '@' || domain) or
$1::text = ('@' || a.handle || '@' || domain) or
(a.origin is null and
$1::text = a.handle or
$1::text = ('@' || a.handle))
]];
};
actor_save = {
params = {
uint64, --id
rawstring, --nym
rawstring, --handle
rawstring, --bio
rawstring, --epithet
rawstring, --avataruri
uint64, --avatarid
uint16, --rank
uint32 --quota
}, cmd = true, sql = [[
update parsav_actors set
nym = $2::text,
handle = $3::text,
bio = $4::text,
epithet = $5::text,
avataruri = $6::text,
avatarid = $7::bigint,
rank = $8::smallint,
quota = $9::integer
--invites are controlled by their own specialized routines
where id = $1::bigint
]];
};
actor_create = {
params = {
rawstring, rawstring, uint64, lib.store.timepoint,
rawstring, rawstring, lib.mem.ptr(uint8),
rawstring, uint16, uint32
};
sql = [[
insert into parsav_actors (
nym,handle,
origin,knownsince,
bio,avataruri,key,
epithet,rank,quota
) values ($1::text, $2::text,
case when $3::bigint = 0 then null
else $3::bigint end,
to_timestamp($4::bigint),
$5::bigint, $6::bigint, $7::bytea,
$8::text, $9::smallint, $10::integer
) returning id
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name from parsav_auth as a
................................................................................
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
extract(epoch from knownsince)::bigint,
handle ||'@'||
(select value from parsav_config
where key='domain' limit 1) as xid
from parsav_actors where origin is null
]];
};
actor_enum = {
params = {}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors a
left join parsav_servers s on s.id = a.origin
]];
};
actor_stats = {
params = {uint64}, sql = ([[
with tweets as (
select from parsav_posts where author = $1::bigint
................................................................................
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
var a: lib.mem.ptr(lib.store.actor)
var av: rawstring, avlen: intptr
var nym: rawstring, nymlen: intptr
var bio: rawstring, biolen: intptr
var epi: rawstring, epilen: intptr
if r:null(row,5) then avlen = 0 av = nil else
av = r:string(row,5)
avlen = r:len(row,5)+1
end
if r:null(row,1) then nymlen = 0 nym = nil else
nym = r:string(row,1)
nymlen = r:len(row,1)+1
end
if r:null(row,4) then biolen = 0 bio = nil else
bio = r:string(row,4)
biolen = r:len(row,4)+1
................................................................................
epilen = r:len(row,9)+1
end
a = [ lib.str.encapsulate(lib.store.actor, {
nym = {`nym, `nymlen};
bio = {`bio, `biolen};
epithet = {`epi, `epilen};
avatar = {`av,`avlen};
handle = {`r:string(row, 2); `r:len(row,2) + 1};
xid = {`r:string(row, 11); `r:len(row,11) + 1};
}) ]
a.ptr.id = r:int(uint64, row, 0);
a.ptr.rights = lib.store.rights_default();
a.ptr.rights.rank = r:int(uint16, row, 6);
a.ptr.rights.quota = r:int(uint32, row, 7);
a.ptr.knownsince = r:int(int64,row, 10);
if r:null(row,8) then
a.ptr.key.ct = 0 a.ptr.key.ptr = nil
else
a.ptr.key = r:bin(row,8)
end
if r:null(row,3) then a.ptr.origin = 0
else a.ptr.origin = r:int(uint64,row,3) end
return a
end
local privmap = lib.store.privmap
local checksha = function(src, hash, origin, username, pw)
local validate = function(kind, cred, credlen)
................................................................................
local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))
local privupdate = terra(
src: &lib.store.source,
ac: &lib.store.actor
): {}
var pdef = lib.store.rights_default().powers
var map = array([privmap])
for i=0, [map.type.N] do
var d = pdef and map[i].priv
var u = ac.rights.powers and map[i].priv
queries.actor_power_delete.exec(src, ac.id, map[i].name)
if d:sz() > 0 and u:sz() == 0 then
lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
................................................................................
return a
end
end];
actor_enum = [terra(src: &lib.store.source)
var r = queries.actor_enum.exec(src)
if r.sz == 0 then
return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
else defer r:free()
var mem = lib.mem.heapa([&lib.store.actor], r.sz)
for i=0,r.sz do
mem.ptr[i] = row_to_actor(&r, i).ptr
mem.ptr[i].source = src
end
return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
end
end];
actor_enum_local = [terra(src: &lib.store.source)
var r = queries.actor_enum_local.exec(src)
if r.sz == 0 then
return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
else defer r:free()
var mem = lib.mem.heapa([&lib.store.actor], r.sz)
for i=0,r.sz do
mem.ptr[i] = row_to_actor(&r, i).ptr
mem.ptr[i].source = src
end
return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
end
end];
actor_auth_how = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: rawstring
................................................................................
var a = row_to_actor(&r, 0)
a.ptr.source = src
var au = [lib.stat(lib.store.auth)] { ok = true }
au.val.aid = aid
au.val.uid = a.ptr.id
if not r:null(0,13) then -- restricted?
au.val.privs:clear()
(au.val.privs.post << r:bool(0,14))
(au.val.privs.edit << r:bool(0,15))
(au.val.privs.acct << r:bool(0,16))
(au.val.privs.upload << r:bool(0,17))
(au.val.privs.censor << r:bool(0,18))
(au.val.privs.admin << r:bool(0,19))
else au.val.privs:fill() end
return au, a
end
::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
[lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
end];
actor_powers_fetch = getpow;
actor_save = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): {}
queries.actor_save.exec(src,
ac.id, ac.nym, ac.handle,
ac.bio, ac.epithet, ac.avatar,
ac.avatarid, ac.rights.rank, ac.rights.quota)
end];
actor_save_privs = privupdate;
actor_create = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): uint64
var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
if r.sz == 0 then lib.bail('failed to create actor!') end
ac.id = r:int(uint64,0,0)
-- check against default rights, insert records for wherever powers differ
lib.dbg('created new actor, establishing powers')
privupdate(src,ac)
lib.dbg('powers established')
return ac.id
end];
auth_enum_uid = [terra(
src: &lib.store.source,
uid: uint64
): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
var r = queries.auth_enum_uid.exec(src,uid)
if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
|
| > > > > > > > > > > > > | | | | | > | > | | < > | > > > > > > > > > > > | > > > > | > | | | | | | | | | | | | | | | | | | | | > > | | | > > > > > |
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 .. 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 .. 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 ... 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 ... 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 ... 836 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 ... 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 .... 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 .... 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 .... 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 |
actor_fetch_uid = {
params = {uint64}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
a.invites
from parsav_actors as a
left join parsav_servers as s
on a.origin = s.id
where a.id = $1::bigint
]];
};
................................................................................
actor_fetch_xid = {
params = {pstring}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
a.invites,
coalesce(s.domain,
(select value from parsav_config
where key='domain' limit 1)) as domain
from parsav_actors as a
left join parsav_servers as s
................................................................................
where $1::text = (a.handle || '@' || domain) or
$1::text = ('@' || a.handle || '@' || domain) or
(a.origin is null and
$1::text = a.handle or
$1::text = ('@' || a.handle))
]];
};
actor_purge_uid = {
params = {uint64}, cmd = true, sql = [[
with d as ( -- cheating
delete from parsav_sanctions where victim = $1::bigint
)
delete from parsav_actors where id = $1::bigint
]];
};
actor_save = {
params = {
uint64, --id
rawstring, --nym
rawstring, --handle
rawstring, --bio
rawstring, --epithet
rawstring, --avataruri
uint64, --avatarid
uint16, --rank
uint32, --quota
uint32 --invites
}, cmd = true, sql = [[
update parsav_actors set
nym = $2::text,
handle = $3::text,
bio = $4::text,
epithet = $5::text,
avataruri = $6::text,
avatarid = $7::bigint,
rank = $8::smallint,
quota = $9::integer,
invites = $10::integer
where id = $1::bigint
]];
};
actor_create = {
params = {
rawstring, rawstring, uint64, lib.store.timepoint,
rawstring, rawstring, lib.mem.ptr(uint8),
rawstring, uint16, uint32, uint32
};
sql = [[
insert into parsav_actors (
nym,handle,
origin,knownsince,
bio,avataruri,key,
epithet,rank,quota,
invites
) values ($1::text, $2::text,
case when $3::bigint = 0 then null
else $3::bigint end,
to_timestamp($4::bigint),
$5::bigint, $6::bigint, $7::bytea,
$8::text, $9::smallint, $10::integer,
$11::integer
) returning id
]];
};
actor_auth_pw = {
params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
select a.aid, a.uid, a.name from parsav_auth as a
................................................................................
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
extract(epoch from knownsince)::bigint,
'@' || handle,
invites
from parsav_actors where origin is null
order by nullif(rank,0) nulls last, handle
]];
};
actor_enum = {
params = {}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
invites
from parsav_actors a
left join parsav_servers s on s.id = a.origin
order by nullif(a.rank,0) nulls last, a.handle, a.origin
]];
};
actor_stats = {
params = {uint64}, sql = ([[
with tweets as (
select from parsav_posts where author = $1::bigint
................................................................................
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
var a: lib.mem.ptr(lib.store.actor)
var av: rawstring, avlen: intptr
var nym: rawstring, nymlen: intptr
var bio: rawstring, biolen: intptr
var epi: rawstring, epilen: intptr
var origin: uint64 = 0
var handle = r:_string(row, 2)
if not r:null(row,3) then origin = r:int(uint64,row,3) end
var avia = lib.str.acc {buf=nil}
if origin == 0 then
avia:compose('/avi/',handle)
av = avia.buf
avlen = avia.sz+1
elseif r:null(row,5) then
av = r:string(row,5)
avlen = r:len(row,5)+1
else
av = '/s/default-avatar.webp'
avlen = 22
end
if r:null(row,1) then nymlen = 0 nym = nil else
nym = r:string(row,1)
nymlen = r:len(row,1)+1
end
if r:null(row,4) then biolen = 0 bio = nil else
bio = r:string(row,4)
biolen = r:len(row,4)+1
................................................................................
epilen = r:len(row,9)+1
end
a = [ lib.str.encapsulate(lib.store.actor, {
nym = {`nym, `nymlen};
bio = {`bio, `biolen};
epithet = {`epi, `epilen};
avatar = {`av,`avlen};
handle = {`handle.ptr, `handle.ct + 1};
xid = {`r:string(row, 11); `r:len(row,11) + 1};
}) ]
a.ptr.id = r:int(uint64, row, 0);
a.ptr.rights = lib.store.rights_default();
a.ptr.rights.rank = r:int(uint16, row, 6);
a.ptr.rights.quota = r:int(uint32, row, 7);
a.ptr.rights.invites = r:int(uint32, row, 12);
a.ptr.knownsince = r:int(int64,row, 10);
if r:null(row,8) then
a.ptr.key.ct = 0 a.ptr.key.ptr = nil
else
a.ptr.key = r:bin(row,8)
end
a.ptr.origin = origin
if avia.buf ~= nil then avia:free() end
return a
end
local privmap = lib.store.privmap
local checksha = function(src, hash, origin, username, pw)
local validate = function(kind, cred, credlen)
................................................................................
local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))
local privupdate = terra(
src: &lib.store.source,
ac: &lib.store.actor
): {}
var pdef: lib.store.powerset pdef:clear()
var map = array([privmap])
for i=0, [map.type.N] do
var d = pdef and map[i].priv
var u = ac.rights.powers and map[i].priv
queries.actor_power_delete.exec(src, ac.id, map[i].name)
if d:sz() > 0 and u:sz() == 0 then
lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
................................................................................
return a
end
end];
actor_enum = [terra(src: &lib.store.source)
var r = queries.actor_enum.exec(src)
if r.sz == 0 then
return [lib.mem.lstptr(lib.store.actor)].null()
else defer r:free()
var mem = lib.mem.heapa([lib.mem.ptr(lib.store.actor)], r.sz)
for i=0,r.sz do
mem.ptr[i] = row_to_actor(&r, i)
mem(i).ptr.source = src
end
return [lib.mem.lstptr(lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
end
end];
actor_enum_local = [terra(src: &lib.store.source)
var r = queries.actor_enum_local.exec(src)
if r.sz == 0 then
return [lib.mem.lstptr(lib.store.actor)].null()
else defer r:free()
var mem = lib.mem.heapa([lib.mem.ptr(lib.store.actor)], r.sz)
for i=0,r.sz do
mem.ptr[i] = row_to_actor(&r, i)
mem(i).ptr.source = src
end
return [lib.mem.lstptr(lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
end
end];
actor_auth_how = [terra(
src: &lib.store.source,
ip: lib.store.inet,
username: rawstring
................................................................................
var a = row_to_actor(&r, 0)
a.ptr.source = src
var au = [lib.stat(lib.store.auth)] { ok = true }
au.val.aid = aid
au.val.uid = a.ptr.id
if not r:null(0,14) then -- restricted?
au.val.privs:clear()
(au.val.privs.post << r:bool(0,15))
(au.val.privs.edit << r:bool(0,16))
(au.val.privs.acct << r:bool(0,17))
(au.val.privs.upload << r:bool(0,18))
(au.val.privs.censor << r:bool(0,19))
(au.val.privs.admin << r:bool(0,20))
else au.val.privs:fill() end
return au, a
end
::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
[lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
end];
actor_powers_fetch = getpow;
actor_save = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): {}
var avatar = ac.avatar
if ac.origin == 0 then avatar = nil end
queries.actor_save.exec(src,
ac.id, ac.nym, ac.handle,
ac.bio, ac.epithet, avatar,
ac.avatarid, ac.rights.rank, ac.rights.quota, ac.rights.invites)
end];
actor_save_privs = privupdate;
actor_create = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): uint64
var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota,ac.rights.invites)
if r.sz == 0 then lib.bail('failed to create actor!') end
ac.id = r:int(uint64,0,0)
-- check against default rights, insert records for wherever powers differ
lib.dbg('created new actor, establishing powers')
privupdate(src,ac)
lib.dbg('powers established')
return ac.id
end];
actor_purge_uid = [terra(
src: &lib.store.source,
uid: uint64
) queries.actor_purge_uid.exec(src,uid) end];
auth_enum_uid = [terra(
src: &lib.store.source,
uid: uint64
): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
var r = queries.auth_enum_uid.exec(src,uid)
if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
|
Modified backend/schema/pgsql.sql from [b4d8dee98e] to [72a3e65e6e].
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
on delete cascade, -- null origin = local actor
knownsince timestamp not null default now(),
bio text,
avatarid bigint, -- artifact id, null if remote
avataruri text, -- null if local
rank smallint not null default 0,
quota integer not null default 1000,
key bytea, -- private if localactor; public if remote
epithet text,
authtime timestamp not null default now(), -- cookies earlier than this timepoint will not be accepted
unique (handle,origin)
);
................................................................................
create table parsav_room_members (
room bigint not null references parsav_rooms(id) on delete cascade,
member bigint not null references parsav_actors(id) on delete cascade,
rank smallint not null default 0,
admin boolean not null default false, -- non-admins with rank can only moderate + invite
title text, -- admin-granted title like reddit flair
vouchedby bigint references parsav_actors(id)
);
create table parsav_invites (
id bigint primary key default (1+random()*(2^63-1))::bigint,
-- when a user is created from an invite, the invite is deleted and the invite
-- ID becomes the user ID. privileges granted on the invite ID during the invite
-- process are thus inherited by the user
|
>
|
|
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
...
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
on delete cascade, -- null origin = local actor knownsince timestamp not null default now(), bio text, avatarid bigint, -- artifact id, null if remote avataruri text, -- null if local rank smallint not null default 0, quota integer not null default 1000, invites integer not null default 0, key bytea, -- private if localactor; public if remote epithet text, authtime timestamp not null default now(), -- cookies earlier than this timepoint will not be accepted unique (handle,origin) ); ................................................................................ create table parsav_room_members ( room bigint not null references parsav_rooms(id) on delete cascade, member bigint not null references parsav_actors(id) on delete cascade, rank smallint not null default 0, admin boolean not null default false, -- non-admins with rank can only moderate + invite title text, -- admin-granted title like reddit flair vouchedby bigint references parsav_actors(id) on delete set null ); create table parsav_invites ( id bigint primary key default (1+random()*(2^63-1))::bigint, -- when a user is created from an invite, the invite is deleted and the invite -- ID becomes the user ID. privileges granted on the invite ID during the invite -- process are thus inherited by the user |
Modified config.lua from [6a4b9180fe] to [5a4f5a8d5b].
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
-- we should add support for content-encoding headers and pre-compress
-- the damn things before compiling (also making the binary smaller)
{'style.css', 'text/css'};
{'live.js', 'text/javascript'}; -- rrrrrrrr
{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
{'padlock.svg', 'image/svg+xml'};
{'warn.svg', 'image/svg+xml'};
{'query.svg', 'image/svg+xml'};
};
default_ui_accent = tonumber(default('parsav_ui_default_accent',323));
}
if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then
conf.braingeniousmode = true -- SOUND GENERAL QUARTERS
end
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
|
| |
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
-- we should add support for content-encoding headers and pre-compress
-- the damn things before compiling (also making the binary smaller)
{'style.css', 'text/css'};
{'live.js', 'text/javascript'}; -- rrrrrrrr
{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
{'padlock.svg', 'image/svg+xml'};
{'warn.svg', 'image/svg+xml'};
{'query.webp', 'image/webp'};
};
default_ui_accent = tonumber(default('parsav_ui_default_accent',323));
}
if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then
conf.braingeniousmode = true -- SOUND GENERAL QUARTERS
end
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
|
Modified makefile from [e6a5371547] to [eedbd28993].
1 2 3 4 5 6 7 8 9 10 11 |
dl = git dbg-flags = $(if $(dbg),-g) images = static/default-avatar.webp #$(addsuffix .webp, $(basename $(wildcard static/*.svg))) styles = $(addsuffix .css, $(basename $(wildcard static/*.scss))) parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles) terra $(dbg-flags) $< parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles) env parsav_link=no terra $(dbg-flags) $< |
| |
1 2 3 4 5 6 7 8 9 10 11 |
dl = git
dbg-flags = $(if $(dbg),-g)
images = static/default-avatar.webp static/query.webp
#$(addsuffix .webp, $(basename $(wildcard static/*.svg)))
styles = $(addsuffix .css, $(basename $(wildcard static/*.scss)))
parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles)
terra $(dbg-flags) $<
parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles)
env parsav_link=no terra $(dbg-flags) $<
|
Modified mem.t from [1f9397ac82] to [a0c3213659].
123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
end
end
return t
end
m.ptr = terralib.memoize(function(ty) return mkptr(ty, true) end)
m.ref = terralib.memoize(function(ty) return mkptr(ty, false) end)
m.vec = terralib.memoize(function(ty)
local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
v.entries = {
{field = 'storage', type = m.ptr(ty)};
{field = 'sz', type = intptr};
{field = 'run', type = intptr};
|
> |
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
end
end
return t
end
m.ptr = terralib.memoize(function(ty) return mkptr(ty, true) end)
m.ref = terralib.memoize(function(ty) return mkptr(ty, false) end)
m.lstptr = function(ty) return m.ptr(m.ptr(ty)) end -- make code more readable
m.vec = terralib.memoize(function(ty)
local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
v.entries = {
{field = 'storage', type = m.ptr(ty)};
{field = 'sz', type = intptr};
{field = 'run', type = intptr};
|
Modified mgtool.t from [39281cf1cf] to [ee3dfa15a8].
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ... 130 131 132 133 134 135 136 137 138 139 140 141 142 143 ... 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 ... 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 |
local ctlcmds = {
{ 'start', 'start a new instance of the server' };
{ 'stop', 'stop a running instance' };
{ 'ls', 'list all running instances' };
{ 'attach', 'capture log output from a running instance' };
{ 'db', 'set up and manage the database' };
{ 'user', 'manage users, privileges, and credentials'};
{ 'mkroot <handle>', 'establish a new root user with the given handle' };
{ 'actor <xid> purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' };
{ 'actor <xid> create', 'instantiate a new actor' };
{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
{ 'conf', 'manage the server configuration'};
{ 'serv dl', 'initiate an update cycle over foreign actors' };
{ 'tl', 'print the current local timeline to standard out' };
{ 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
}
local cmdhelp = function(tbl)
local str = '\ncommands:\n'
................................................................................
if acks(i).success then
lib.report('instance #',num,' reports successful ',rep)
else
lib.report('instance #',num,' reports failed ',rep)
end
end
end
local emp = lib.ipc.global_emperor
local terra entry_mgtool(argc: int, argv: &rawstring): int
if argc < 1 then lib.bail('bad invocation!') end
lib.noise.init(2)
[lib.init]
................................................................................
return 1
end
if dbmode.arglist.ct < 1 then goto cmderr end
srv:setup(cnf)
if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
lib.report('initializing new database structure for domain ', dbmode.arglist(1))
if dlg:dbsetup() then
srv:conprep(lib.store.prepmode.conf)
dlg:conf_set('instance-name', dbmode.arglist(1))
do var sec: int8[65] gensec(&sec[0])
dlg:conf_set('server-secret', &sec[0])
end
lib.report('database setup complete; use mkroot to create an administrative user')
else lib.bail('initialization process interrupted') end
elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then
var confirmstrs = array(
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst'
)
var cfmstr: int8[64] cfmstr[0] = 0
var tdx = lib.osclock.time(nil) / 60
for i=0,3 do
if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end
lib.str.cat(&cfmstr[0], confirmstrs[(tdx ^ (173*i)) % [confirmstrs.type.N]])
end
if dbmode.arglist.ct == 1 then
lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0])
elseif dbmode.arglist.ct == 2 then
if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
lib.warn('completely obliterating all data!')
dlg:obliterate_everything()
................................................................................
dlg:conf_set('master',root.handle)
lib.report('created new administrator')
if mg then
var tmppw: int8[33]
pwset(dlg, &tmppw, ruid, false)
lib.report('temporary root pw: ', {&tmppw[0], 32})
end
else goto cmderr end
elseif lib.str.cmp(mode.arglist(0),'user') == 0 then
var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
if umode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
{ 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
{ 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
{ 'user <handle> emasculate', 'strip all administrative powers from a user' };
{ 'user <handle> forgive', 'restore all default powers to a user' };
{ 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
}) ]
return 1
end
if umode.arglist.ct >= 3 then
var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
var handle = umode.arglist(0)
var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
if not usr then lib.bail('unknown handle') end
var newprivs = usr.ptr.rights.powers
var map = array([lib.store.privmap])
if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then
if grant
then newprivs:fill()
else newprivs:clear()
end
|
| > < < < > > > > > > > > > > > > > > > > > > < < < < | < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | | > > > > > > > > > > | | > |
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ... 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 ... 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 ... 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 |
local ctlcmds = {
{ 'start', 'start a new instance of the server' };
{ 'stop', 'stop a running instance' };
{ 'ls', 'list all running instances' };
{ 'attach', 'capture log output from a running instance' };
{ 'db', 'set up and manage the database' };
{ 'user', 'create and manage users, privileges, and credentials'};
{ 'actor', 'manage and purge actors, epithets, and ranks'};
{ 'mkroot <handle>', 'establish a new root user with the given handle' };
{ 'conf', 'manage the server configuration'};
{ 'grow <count> [<acl>]', 'grant a new round of invites to all users, or those who match the given ACL' };
{ 'serv dl', 'initiate an update cycle over foreign actors' };
{ 'tl', 'print the current local timeline to standard out' };
{ 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
}
local cmdhelp = function(tbl)
local str = '\ncommands:\n'
................................................................................
if acks(i).success then
lib.report('instance #',num,' reports successful ',rep)
else
lib.report('instance #',num,' reports failed ',rep)
end
end
end
local terra gen_cfstr(cfmstr: rawstring, seed: intptr)
var confirmstrs = array(
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst'
)
var tdx = lib.osclock.time(nil) / 60
cfmstr[0] = 0
for i=0,3 do
if i ~= 0 then lib.str.cat(cfmstr, '-') end
lib.str.cat(cfmstr, confirmstrs[(seed ^ tdx ^ (173*i)) % [confirmstrs.type.N]])
end
end
local emp = lib.ipc.global_emperor
local terra entry_mgtool(argc: int, argv: &rawstring): int
if argc < 1 then lib.bail('bad invocation!') end
lib.noise.init(2)
[lib.init]
................................................................................
return 1
end
if dbmode.arglist.ct < 1 then goto cmderr end
srv:setup(cnf)
if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
lib.report('initializing new database structure for domain ', dbmode.arglist(1))
dlg:tx_enter()
if dlg:dbsetup() then
srv:conprep(lib.store.prepmode.conf)
dlg:conf_set('instance-name', dbmode.arglist(1))
dlg:conf_set('domain', dbmode.arglist(1))
do var sec: int8[65] gensec(&sec[0])
dlg:conf_set('server-secret', &sec[0])
dlg:conf_set('server-secret', &sec[0])
end
lib.report('database setup complete; use mkroot to create an administrative user')
else lib.bail('initialization process interrupted') end
dlg:tx_complete()
elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then
var cfmstr: int8[64] gen_cfstr(&cfmstr[0],0)
if dbmode.arglist.ct == 1 then
lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0])
elseif dbmode.arglist.ct == 2 then
if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
lib.warn('completely obliterating all data!')
dlg:obliterate_everything()
................................................................................
dlg:conf_set('master',root.handle)
lib.report('created new administrator')
if mg then
var tmppw: int8[33]
pwset(dlg, &tmppw, ruid, false)
lib.report('temporary root pw: ', {&tmppw[0], 32})
end
else goto cmderr end
elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then
var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
if umode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' actor ', umode.type.helptxt.flags, ' <xid> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
{ 'actor <xid> rank <value>', 'set an actor\'s rank to <value> (remote actors cannot exercise rank-related powers, but benefit from rank immunities)' };
{ 'actor <xid> degrade', 'alias for `actor <xid> rank 0`' };
{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
{ 'actor <xid> instantiate', 'instantiate a remote actor, retrieving their profile and posts even if no one follows them' };
{ 'actor <xid> proscribe', 'globally ban an actor from interacting with your server' };
{ 'actor <xid> rehabilitate', 'lift a proscription on an actor' };
{ 'actor <xid> purge-all <confirm-str>', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' };
}) ]
return 1
end
if umode.arglist.ct >= 2 then
var degrade = lib.str.cmp(umode.arglist(1),'degrade') == 0
var xid = umode.arglist(0)
var usr = dlg:actor_fetch_xid(pstr {ptr=xid, ct=lib.str.sz(xid)})
if not usr then lib.bail('no such actor') end
if degrade or lib.str.cmp(umode.arglist(1),'rank') == 0 then
var rank: uint16
if degrade and umode.arglist.ct == 2 then
rank = 0
elseif (not degrade) and umode.arglist.ct == 3 then
var r, ok = lib.math.decparse(pstr {
ptr = umode.arglist(2);
ct = lib.str.sz(umode.arglist(2));
})
if not ok then goto cmderr end
rank = r
else goto cmderr end
usr.ptr.rights.rank = rank
dlg:actor_save(usr.ptr)
lib.report('set user rank')
elseif umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(1),'bestow') == 0 then
if umode.arglist(2)[0] == 0
then usr.ptr.epithet = nil
else usr.ptr.epithet = umode.arglist(2)
end
dlg:actor_save(usr.ptr)
lib.report('bestowed a new epithet on ', usr.ptr.xid)
elseif lib.str.cmp(umode.arglist(1),'purge-all') == 0 then
var cfmstr: int8[64] gen_cfstr(&cfmstr[0],usr.ptr.id)
if umode.arglist.ct == 2 then
lib.bail('you are attempting to completely purge the actor ', usr.ptr.xid, ' and all related content from the database! if you really want to do this, pass the confirmation string ', &cfmstr[0])
elseif umode.arglist.ct == 3 then
if lib.str.ncmp(&cfmstr[0],umode.arglist(2),64) ~= 0 then
lib.bail('you have supplied an invalid confirmation string; if you really want to purge this actor, pass ', &cfmstr[0])
end
lib.warn('completely purging actor ', usr.ptr.xid, ' and all related content from database')
dlg:actor_purge_uid(usr.ptr.id)
lib.report('actor purged')
else goto cmderr end
else goto cmderr end
else goto cmderr end
elseif lib.str.cmp(mode.arglist(0),'user') == 0 then
var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
if umode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
{ 'user <handle> create', 'add a new user' };
{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
{ 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
{ 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
{ 'user <handle> emasculate', 'strip all administrative powers and rank from a user' };
{ 'user <handle> forgive', 'restore all default powers to a user' };
{ 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
}) ]
return 1
end
var handle = umode.arglist(0)
var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
if umode.arglist.ct == 2 and lib.str.cmp(umode.arglist(1),'create')==0 then
if usr:ref() then lib.bail('that user already exists') end
if not lib.store.actor.handle_validate(handle) then
lib.bail('invalid user handle') end
var kbuf: uint8[lib.crypt.const.maxdersz]
var na = lib.store.actor.mk(&kbuf[0])
na.handle = handle
dlg:actor_create(&na)
lib.report('created new user @',na.handle,'; assign credentials to enable login')
elseif umode.arglist.ct >= 3 then
var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
if not usr then lib.bail('no such user') end
if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
var newprivs = usr.ptr.rights.powers
var map = array([lib.store.privmap])
if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then
if grant
then newprivs:fill()
else newprivs:clear()
end
|
Modified parsav.t from [4b4b2876e4] to [2bbe093dad].
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
'http', 'html', 'session', 'tpl', 'store', 'acl';
'smackdown'; -- md-alike parser
}
local be = {}
for _, b in pairs(config.backends) do
be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))
lib.cmdparse = terralib.loadfile('cmdparse.t')()
do local collate = function(path,f, ...)
return loadfile(path..'/'..f..'.lua')(path, ...)
|
| |
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
'http', 'html', 'session', 'tpl', 'store', 'acl';
'smackdown'; -- md-alike parser
}
local be = {}
for _, b in pairs(config.backends) do
be[#be+1] = terralib.loadfile(string.format('backend/%s.t',b))()
end
lib.store.backends = global(`array([be]))
lib.cmdparse = terralib.loadfile('cmdparse.t')()
do local collate = function(path,f, ...)
return loadfile(path..'/'..f..'.lua')(path, ...)
|
Modified render/conf.t from [fa178f73b5] to [60d6b764a8].
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
{url = 'qnt', title = 'quarantine', render = 'quarantine'};
{url = 'acl', title = 'access control shortcuts', render = 'acl'};
{url = 'rooms', title = 'chatrooms', render = 'rooms'};
{url = 'circles', title = 'circles', render = 'circles'};
{url = 'srv', title = 'server settings', render = 'srv'};
{url = 'brand', title = 'instance branding', render = 'rebrand'};
{url = 'censor', title = 'censorship & badthink suppression', render = 'rebrand'};
{url = 'users', title = 'user accounting', render = 'users'};
}
local path = symbol(lib.mem.ptr(pref))
local co = symbol(&lib.srv.convo)
................................................................................
local terra
render_conf([co], [path], notify: pstr)
var menu: lib.str.acc menu:init(64):lpush('<hr>') defer menu:free()
-- build menu
do var p = co.who.rights.powers
if p.config() then menu:lpush '<a href="/conf/srv">server settings</a>' end
if p.rebrand() then menu:lpush '<a href="/conf/brand">instance branding</a>' end
if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end
if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end
end
-- select the appropriate panel
var [panel] = pstr { ptr = ''; ct = 0 }
if path.ct >= 2 then [invoker] end
-- avoid the hr if we didn't add any elements
|
>
>
|
<
>
>
>
>
>
|
|
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
{url = 'qnt', title = 'quarantine', render = 'quarantine'};
{url = 'acl', title = 'access control shortcuts', render = 'acl'};
{url = 'rooms', title = 'chatrooms', render = 'rooms'};
{url = 'circles', title = 'circles', render = 'circles'};
{url = 'srv', title = 'server settings', render = 'srv'};
{url = 'brand', title = 'instance branding', render = 'rebrand'};
{url = 'badge', title = 'user badges', render = 'badge'};
{url = 'emoji', title = 'custom emoji packs', render = 'emojo'};
{url = 'censor', title = 'censorship & badthink suppression', render = 'rebrand'};
{url = 'users', title = 'user accounting', render = 'users'};
}
local path = symbol(lib.mem.ptr(pref))
local co = symbol(&lib.srv.convo)
................................................................................
local terra
render_conf([co], [path], notify: pstr)
var menu: lib.str.acc menu:init(64):lpush('<hr>') defer menu:free()
-- build menu
do var p = co.who.rights.powers
if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end
if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end
if p.config() then menu:lpush([
'<a href="/conf/srv">server & policy</a>' ..
'<a href="/conf/badge">badges</a>' ..
'<a href="/conf/emoji">emoji packs</a>'
]) end
if p.rebrand() then menu:lpush '<a href="/conf/brand">instance branding</a>' end
end
-- select the appropriate panel
var [panel] = pstr { ptr = ''; ct = 0 }
if path.ct >= 2 then [invoker] end
-- avoid the hr if we didn't add any elements
|
Modified render/conf/users.t from [6e4ba75dd2] to [4494d99300].
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
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > < > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | |
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 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 |
-- vim: ft=terra local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local P = lib.str.plit local terra cs(s: rawstring) return pstr { ptr = s, ct = lib.str.sz(s) } end local terra regalia(acc: &lib.str.acc, rank: uint16) switch rank do -- TODO customizability case [uint16](1) then acc:lpush('👑') end case [uint16](2) then acc:lpush('🔱') end case [uint16](3) then acc:lpush('⚜️') end case [uint16](4) then acc:lpush('🗡') end case [uint16](5) then acc:lpush('🗝') end else acc:lpush('🕴') end end local num_field = macro(function(acc,name,lbl,min,max,value) name = name:asvalue() lbl = lbl:asvalue() return quote var decbuf: int8[21] in acc:lpush([string.format('<div class="elem small"><label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name)]) :push(lib.math.decstr(min, &decbuf[20]),0) :lpush('" max="'):push(lib.math.decstr(max, &decbuf[20]),0) :lpush('" value="'):push(lib.math.decstr(value, &decbuf[20]),0):lpush('"></div>') end end) local terra push_checkbox(acc: &lib.str.acc, name: pstr, lbl: pstr, on: bool, enabled: bool) acc:lpush('<label><input type="checkbox" name="'):ppush(name):lpush('"') if on then acc:lpush(' checked') end if not enabled then acc:lpush(' disabled') end acc:lpush('> '):ppush(lbl):lpush('</label>') end local mode_local, mode_remote, mode_staff, mode_peers, mode_peons, mode_all = 0,1,2,3,4,5 local terra render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr if path.ct == 3 then var uid, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct) if not ok then goto e404 end var user = co.srv:actor_fetch_uid(uid) -- FIXME allow xids as well, for manual queries if not user then goto e404 end defer user:free() if not co.who:overpowers(user.ptr) then goto e403 end var islinkct = false var cinp: lib.str.acc cinp:init(128) var clnk: lib.str.acc clnk:compose('<hr>') cinp:lpush('<div class="elem-group">') if co.who.rights.powers.herald() then var sanitized: pstr if user.ptr.epithet == nil then sanitized = pstr {ptr='', ct=0} else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true) end cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>') if user.ptr.epithet ~= nil then sanitized:free() end end if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then var max = co.who.rights.rank if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end var min = co.srv.cfg.nranks if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank) end if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then var min = 0 if not (co.who.rights.powers.discipline() or co.who.rights.powers.demote() and co.who.rights.powers.invite()) then min = user.ptr.rights.invites end var max = co.srv.cfg.maxinvites if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites) end cinp:lpush('</div><div class="check-panel">') if (user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or (user.ptr.rights.rank > 0 and co.who.rights.powers.demote()) then push_checkbox(&cinp, 'staff', 'site staff member', user.ptr.rights.rank > 0, true) end cinp:lpush('</div>') if co.who.rights.powers.elevate() or co.who.rights.powers.demote() then var map = array([lib.store.privmap]) cinp:lpush('<label>powers</label><div class="check-panel">') for i=0, [map.type.N] do if (co.who.rights.powers and map[i].priv) == map[i].priv then var name: int8[64] var on = (user.ptr.rights.powers and map[i].priv) == map[i].priv var enabled = (on and co.who.rights.powers.demote()) or ((not on) and co.who.rights.powers.elevate()) lib.str.cpy(&name[0], 'allow-') lib.str.ncpy(&name[6], map[i].name.ptr, map[i].name.ct) push_checkbox(&cinp, pstr{ptr=&name[0],ct=map[i].name.ct+6}, map[i].name, on, enabled) end end cinp:lpush('</div>') end -- TODO black mark system? e.g. resolution option for badthink reports -- adds a black mark to the offending user; they can be automatically banned -- or brought up for review after a certain number of offenses; possibly lower -- set of default privs for marked users 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 unym: lib.str.acc unym:init(64) unym:lpush('<a href="/') if user(0).origin ~= 0 then unym:lpush('@') end do var sanxid = lib.html.sanitize(user(0).xid, true) unym:ppush(sanxid) sanxid:free() end unym:lpush('" class="id">') lib.render.nym(user.ptr,0,&unym) unym:lpush('</a>') var pg = data.view.conf_user_ctl { name = unym:finalize(); inputcontent = cinpp; linkcontent = clnkp; } var ret = pg:tostr() pg.name:free() if islinkct then clnkp:free() end return ret else var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all') var idbuf: int8[lib.math.shorthand.maxlen] var ulst: lib.str.acc ulst:init(256) var mode: uint8 = mode_local var modestr = co:pgetv('show') ulst:lpush('<div style="text-align: right"><em>showing ') for i=0,[modes.type.N] do if modestr:ref() and modes[i]:cmp(modestr) then mode = i end end for i=0,[modes.type.N] do if i > 0 then ulst:lpush(' · ') end if mode == i then ulst:lpush('<strong>'):ppush(modes[i]):lpush('</strong>') else ulst:lpush('<a href="?show='):ppush(modes[i]):lpush('">') :ppush(modes[i]):lpush('</a>') end end var users: lib.mem.lstptr(lib.store.actor) if mode == mode_local then users = co.srv:actor_enum_local() else users = co.srv:actor_enum() end ulst:lpush('</em></div>') ulst:lpush('<ul class="user-list">') for i=0,users.ct do var usr = users(i).ptr if mode == mode_staff and usr.rights.rank == 0 then goto skip elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip elseif mode == mode_remote and usr.origin == 0 then goto skip elseif mode == mode_peers and usr.epithet == nil then goto skip end var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0]) ulst:lpush('<li>') if usr.rights.rank ~= 0 then ulst:lpush('<span class="regalia">') regalia(&ulst, usr.rights.rank) ulst:lpush('</span>') end if co.who:overpowers(usr) then ulst:lpush('<a class="id" href="users/'):push(&idbuf[0],idlen):lpush('">') lib.render.nym(usr, 0, &ulst) ulst:lpush('</a></li>') else ulst:lpush('<span class="id">') lib.render.nym(usr, 0, &ulst) ulst:lpush('</span></li>') end ::skip::end ulst:lpush('</ul>') return ulst:finalize() end do return pstr.null() end ::e404:: co:complain(404, 'not found', 'there is no user or resource by that identifier on this server') goto quit ::e403:: co:complain(403, 'forbidden', 'you do not have sufficient authority to control that resource') ::quit:: return pstr.null() end return render_conf_users |
Modified render/nym.t from [0d2437aadd] to [74775ce158].
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 |
-- vim: ft=terra local pstr = lib.mem.ptr(int8) local terra cs(s: rawstring) return pstr { ptr = s, ct = lib.str.sz(s) } end local terra render_nym(who: &lib.store.actor, scope: uint64) var n: lib.str.acc n:init(128) var xidsan = lib.html.sanitize(cs(who.xid),false) if who.nym ~= nil and who.nym[0] ~= 0 then var nymsan = lib.html.sanitize(cs(who.nym),false) n:compose('<span class="nym">',nymsan,'</span> [<span class="handle">', xidsan,'</span>]') nymsan:free() else n:compose('<span class="handle">',xidsan,'</span>') end xidsan:free() if who.epithet ~= nil then var episan = lib.html.sanitize(cs(who.epithet),false) n:lpush(' <span class="epithet">'):ppush(episan):lpush('</span>') episan:free() end -- TODO: if scope == chat room then lookup titles in room member db return n:finalize() end return render_nym |
| | > > > > | > > | | | | > | > |
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 |
-- vim: ft=terra local pstr = lib.str.t local terra cs(s: rawstring) return pstr { ptr = s, ct = lib.str.sz(s) } end local terra render_nym(who: &lib.store.actor, scope: uint64, tgt: &lib.str.acc) var acc: lib.str.acc var n: &lib.str.acc if tgt ~= nil then n = tgt else n = &acc n:init(128) end var xidsan = lib.html.sanitize(cs(who.xid),false) if who.nym ~= nil and who.nym[0] ~= 0 then var nymsan = lib.html.sanitize(cs(who.nym),false) n:lpush('<span class="nym">'):ppush(nymsan) :lpush('</span> [<span class="handle">'):ppush(xidsan) :lpush('</span>]') nymsan:free() else n:lpush('<span class="handle">'):ppush(xidsan):lpush('</span>') end xidsan:free() if who.epithet ~= nil then var episan = lib.html.sanitize(cs(who.epithet),false) n:lpush('<span class="epithet">'):ppush(episan):lpush('</span>') episan:free() end -- TODO: if scope == chat room then lookup titles in room member db if tgt == nil then return n:finalize() else return pstr.null() end end return render_nym |
Modified render/profile.t from [5ac1497f7a] to [5d5ed1c86e].
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
local terra cs(s: rawstring)
return pstr { ptr = s, ct = lib.str.sz(s) }
end
local terra
render_profile(co: &lib.srv.convo, actor: &lib.store.actor)
var aux: lib.str.acc
var followed = true -- FIXME
if co.aid ~= 0 and co.who.id == actor.id then
aux:compose('<a class="button" href="/conf/profile?go=/',actor.xid,'">alter</a>')
elseif co.aid ~= 0 then
if not followed then
aux:compose('<button method="post" name="act" value="follow">follow</a>')
elseif not followed then
aux:compose('<button method="post" name="act" value="unfollow">unfollow</a>')
end
aux:lpush('<a href="/'):push(actor.xid,0):lpush('/chat">chat</a>')
if co.who.rights.powers:affect_users() then
aux:lpush('<a class="button" href="/'):push(actor.xid,0):lpush('/ctl">control</a>')
end
else
aux:compose('<a class="button" href="/', actor.xid, '/follow">remote follow</a>')
end
var auxp = aux:finalize()
var avistr: lib.str.acc if actor.origin == 0 then
avistr:compose('/avi/',actor.handle)
end
var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0])
var strfbuf: int8[28*4]
var stats = co.srv:actor_stats(actor.id)
var sn_posts = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
var sn_follows = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
var sn_mutuals = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
var bio = lib.str.plit "<em>tall, dark, and mysterious</em>"
if actor.bio ~= nil then
bio = lib.smackdown.html(cs(actor.bio))
end
var fullname = lib.render.nym(actor,0) defer fullname:free()
var profile = data.view.profile {
nym = fullname;
bio = bio;
xid = cs(actor.xid);
avatar = lib.trn(actor.origin == 0, pstr{ptr=avistr.buf,ct=avistr.sz},
cs(lib.coalesce(actor.avatar, '/s/default-avatar.svg')));
nposts = sn_posts, nfollows = sn_follows;
nfollowers = sn_followers, nmutuals = sn_mutuals;
tweetday = cs(timestr);
timephrase = lib.trn(actor.origin == 0, lib.str.plit'joined', lib.str.plit'known since');
auxbtn = auxp;
}
var ret = profile:tostr()
if actor.origin == 0 then avistr:free() end
auxp:free()
if actor.bio ~= nil then bio:free() end
return ret
end
return render_profile
|
| | | | | | | < < < | > > > > > > > > > > > | < > > > < > |
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
local terra cs(s: rawstring)
return pstr { ptr = s, ct = lib.str.sz(s) }
end
local terra
render_profile(co: &lib.srv.convo, actor: &lib.store.actor)
var aux: lib.str.acc
var followed = false -- FIXME
if co.aid ~= 0 and co.who.id == actor.id then
aux:compose('<a class="button" href="/conf/profile?go=/@',actor.handle,'">alter</a>')
elseif co.aid ~= 0 then
if not followed then
aux:compose('<button method="post" class="pos" name="act" value="follow">follow</button>')
elseif followed then
aux:compose('<button method="post" class="neg" name="act" value="unfollow">unfollow</button>')
end
aux:lpush('<a class="button" href="/'):push(actor.xid,0):lpush('/chat">chat</a>')
if co.who.rights.powers:affect_users() and co.who:overpowers(actor) then
aux:lpush('<a class="button" href="/'):push(actor.xid,0):lpush('/ctl">control</a>')
end
else
aux:compose('<a class="button" href="/', actor.xid, '/follow">remote follow</a>')
end
var auxp = aux:finalize()
var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, ×tr[0])
var strfbuf: int8[28*4]
var stats = co.srv:actor_stats(actor.id)
var sn_posts = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
var sn_follows = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
var sn_mutuals = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
var bio = lib.str.plit "<em>tall, dark, and mysterious</em>"
if actor.bio ~= nil then
bio = lib.smackdown.html(cs(actor.bio))
end
var fullname = lib.render.nym(actor,0,nil) defer fullname:free()
var comments: lib.str.acc comments:init(64)
-- this is really more what epithets are for, i think
--if actor.rights.rank > 0 then comments:lpush('<li>staff member</li>') end
if co.aid ~= 0 and actor.rights.rank ~= 0 then
if co.who:outranks(actor) then
comments:lpush('<li style="--co:50">underling</li>')
elseif actor:outranks(co.who) then
comments:lpush('<li style="--co:-50">outranks you</li>')
end
end
var profile = data.view.profile {
nym = fullname;
bio = bio;
xid = cs(actor.xid);
avatar = cs(actor.avatar);
nposts = sn_posts, nfollows = sn_follows;
nfollowers = sn_followers, nmutuals = sn_mutuals;
tweetday = cs(timestr);
timephrase = lib.trn(actor.origin == 0, lib.str.plit'joined', lib.str.plit'known since');
remarks = '';
auxbtn = auxp;
}
if comments.sz > 0 then profile.remarks = comments:finalize() end
var ret = profile:tostr()
auxp:free()
if actor.bio ~= nil then bio:free() end
if comments.sz > 0 then profile.remarks:free() end
return ret
end
return render_profile
|
Modified render/tweet.t from [2b64155fcc] to [43aca48007].
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0])
var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free()
var idbuf: int8[lib.math.shorthand.maxlen]
var idlen = lib.math.shorthand.gen(p.id, idbuf)
var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
var fullname = lib.render.nym(author,0) defer fullname:free()
var tpl = data.view.tweet {
text = bhtml;
subject = cs(lib.coalesce(p.subject,''));
nym = fullname;
when = cs(×tr[0]);
avatar = cs(lib.trn(author.origin == 0, avistr.buf,
lib.coalesce(author.avatar, '/s/default-avatar.svg')));
acctlink = cs(author.xid);
permalink = permalink:finalize();
attr = ''
}
var attrbuf: int8[32]
if p.accent ~= -1 and p.accent ~= co.ui_hue then
|
| | < |
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0])
var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free()
var idbuf: int8[lib.math.shorthand.maxlen]
var idlen = lib.math.shorthand.gen(p.id, idbuf)
var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
var fullname = lib.render.nym(author,0,nil) defer fullname:free()
var tpl = data.view.tweet {
text = bhtml;
subject = cs(lib.coalesce(p.subject,''));
nym = fullname;
when = cs(×tr[0]);
avatar = cs(author.avatar);
acctlink = cs(author.xid);
permalink = permalink:finalize();
attr = ''
}
var attrbuf: int8[32]
if p.accent ~= -1 and p.accent ~= co.ui_hue then
|
Modified route.t from [35b3cb0b8a] to [2f7668c3df].
245
246
247
248
249
250
251
252
253
254
255
256
257
258
...
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
::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
lib.dbg('updating profile')
co.who.bio = co:postv('bio')._0
co.who.nym = co:postv('nym')._0
if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
................................................................................
if resethue then
co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
co.ui_hue = co.srv.cfg.ui_hue
end
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)
return
end
end
if user_refresh then -- refresh the user info for the renderer
var usr = co.srv:actor_fetch_uid(co.who.id)
lib.mem.heapf(co.who)
co.who = usr.ptr
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
<
<
<
<
<
<
>
>
>
>
>
>
|
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
...
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
328
329
|
::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() -- first things first, do priv checks if path.ct >= 1 then if not co.who.rights.powers.config() and ( path(1):cmp(lib.str.lit 'srv') or path(1):cmp(lib.str.lit 'badge') or path(1):cmp(lib.str.lit 'emoji') ) then goto nopriv elseif not co.who.rights.powers.rebrand() and ( path(1):cmp(lib.str.lit 'brand') ) then goto nopriv elseif not co.who.rights.powers.acct() and ( path(1):cmp(lib.str.lit 'profile') or path(1):cmp(lib.str.lit 'acct') ) then goto nopriv elseif not co.who.rights.powers:affect_users() and ( path(1):cmp(lib.str.lit 'users') ) then goto nopriv end end 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 lib.dbg('updating profile') co.who.bio = co:postv('bio')._0 co.who.nym = co:postv('nym')._0 if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end ................................................................................ if resethue then co.srv:actor_conf_int_reset(co.who.id, 'ui-accent') co.ui_hue = co.srv.cfg.ui_hue end msg = lib.str.plit 'profile changes saved' --user_refresh = true -- not really necessary here, actually 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) return end elseif path(1):cmp(lib.str.lit 'users') and path.ct >= 2 then var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) if ok then var usr = co.srv:actor_fetch_uid(userid) defer usr:free() if not co.who:overpowers(usr.ptr) then goto nopriv end end end if user_refresh then -- refresh the user info for the renderer var usr = co.srv:actor_fetch_uid(co.who.id) lib.mem.heapf(co.who) co.who = usr.ptr |
Modified srv.t from [b092edff32] to [6be667433b].
8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... 644 645 646 647 648 649 650 651 652 653 654 655 656 657 ... 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 ... 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 |
pol_sec: secmode.t
pol_reg: bool
credmgd: bool
maxupsz: intptr
instance: lib.mem.ptr(int8)
overlord: &srv
ui_hue: uint16
}
local struct srv {
sources: lib.mem.ptr(lib.store.source)
webmgr: lib.net.mg_mgr
webcon: &lib.net.mg_connection
cfg: cfgcache
id: rawstring
................................................................................
self.sources(i).backend.actor_auth_pw ~= nil then
var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw)
if aid ~= 0 then
if uid == 0 then
lib.dbg('new user just logged in, creating account entry')
var kbuf: uint8[lib.crypt.const.maxdersz]
var na = lib.store.actor.mk(&kbuf[0])
var newuid: uint64
if self.sources(i).backend.actor_create ~= nil then
newuid = self.sources(i):actor_create(&na)
else newuid = self:actor_create(&na) end
if self.sources(i).backend.actor_auth_register_uid ~= nil then
self.sources(i):actor_auth_register_uid(aid,newuid)
................................................................................
lib.net.mg_mgr_free(&self.webmgr)
for i=0,self.sources.ct do var src = self.sources.ptr + i
lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
src:close()
end
self.sources:free()
end
terra cfgcache:load()
self.instance = self.overlord:conf_get('instance-name')
self.secret = self.overlord:conf_get('server-secret')
do self.pol_reg = false
var sreg = self.overlord:conf_get('policy-self-register')
if sreg:ref() then
if lib.str.cmp(sreg.ptr, 'on') == 0
then self.pol_reg = true
else self.pol_reg = false
end
sreg:free()
end end
do self.credmgd = false
var sreg = self.overlord:conf_get('credential-store')
if sreg:ref() then
if lib.str.cmp(sreg.ptr, 'managed') == 0
then self.credmgd = true
else self.credmgd = false
................................................................................
self.pol_sec = secmode.lockdown
elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then
self.pol_sec = secmode.isolate
end
smode:free()
end
self.ui_hue = config.default_ui_accent
var shue = self.overlord:conf_get('ui-accent')
if shue.ptr ~= nil then
var hue,ok = lib.math.decparse(shue)
if ok then self.ui_hue = hue end
shue:free()
end
end
return {
overlord = srv;
convo = convo;
route = route;
secmode = secmode;
}
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | < < < < < < < | | | < < < < |
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ... 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 ... 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 ... 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 |
pol_sec: secmode.t pol_reg: bool credmgd: bool maxupsz: intptr instance: lib.mem.ptr(int8) overlord: &srv ui_hue: uint16 nranks: uint16 maxinvites: uint16 } local struct srv { sources: lib.mem.ptr(lib.store.source) webmgr: lib.net.mg_mgr webcon: &lib.net.mg_connection cfg: cfgcache id: rawstring ................................................................................ self.sources(i).backend.actor_auth_pw ~= nil then var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw) if aid ~= 0 then if uid == 0 then lib.dbg('new user just logged in, creating account entry') var kbuf: uint8[lib.crypt.const.maxdersz] var na = lib.store.actor.mk(&kbuf[0]) na.handle = newhnd.ptr var newuid: uint64 if self.sources(i).backend.actor_create ~= nil then newuid = self.sources(i):actor_create(&na) else newuid = self:actor_create(&na) end if self.sources(i).backend.actor_auth_register_uid ~= nil then self.sources(i):actor_auth_register_uid(aid,newuid) ................................................................................ lib.net.mg_mgr_free(&self.webmgr) for i=0,self.sources.ct do var src = self.sources.ptr + i lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')') src:close() end self.sources:free() end terra cfgcache:cfint(name: rawstring, default: intptr) var str = self.overlord:conf_get(name) if str.ptr ~= nil then var i,ok = lib.math.decparse(str) if ok then default = i else lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected integer; using default value instead') end str:free() end return default end terra cfgcache:cfbool(name: rawstring, default: bool) var str = self.overlord:conf_get(name) if str.ptr ~= nil then if str:cmp(lib.str.plit 'true') or str:cmp(lib.str.plit 'on') or str:cmp(lib.str.plit 'yes') or str:cmp(lib.str.plit '1') then default = true elseif str:cmp(lib.str.plit 'false') or str:cmp(lib.str.plit 'off') or str:cmp(lib.str.plit 'no') or str:cmp(lib.str.plit '0') then default = false else lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected boolean; using default value instead') end str:free() end return default end terra cfgcache:load() self.instance = self.overlord:conf_get('instance-name') self.secret = self.overlord:conf_get('server-secret') self.pol_reg = self:cfbool('policy-self-register', false) do self.credmgd = false var sreg = self.overlord:conf_get('credential-store') if sreg:ref() then if lib.str.cmp(sreg.ptr, 'managed') == 0 then self.credmgd = true else self.credmgd = false ................................................................................ self.pol_sec = secmode.lockdown elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then self.pol_sec = secmode.isolate end smode:free() end self.ui_hue = self:cfint('ui-accent',config.default_ui_accent) self.nranks = self:cfint('user-ranks',10) self.maxinvites = self:cfint('max-invites',64) end return { overlord = srv; convo = convo; route = route; secmode = secmode; } |
Modified static/style.scss from [a256539ae3] to [f40a20016f].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .. 24 25 26 27 28 29 30 31 32 33 34 35 36 37 .. 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 ... 236 237 238 239 240 241 242 243 244 245 246 247 248 249 ... 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 ... 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 ... 713 714 715 716 717 718 719 |
$default-color: hsl(323,100%,65%);
%sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; }
%serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; }
%teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; }
// @function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) }
@function tone($pct, $alpha: 0) {
@return hsla(var(--hue), 100%, 65% + $pct, 1 + $alpha)
}
:root { --hue: 323; }
body {
@extend %sans;
background-color: tone(-55%);
color: tone(25%);
font-size: 14pt;
margin: 0;
padding: 0;
................................................................................
::placeholder {
color: tone(0,-0.3);
font-style: italic;
}
a[href] {
color: tone(10%);
text-decoration-color: tone(10%,-0.5);
&:hover, &:focus {
color: white;
text-shadow: 0 0 15px tone(20%);
text-decoration-color: tone(10%,-0.1);
outline: none;
}
&.button { @extend %button; }
................................................................................
%button {
@extend %sans;
font-size: 14pt;
box-sizing: border-box;
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;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
background: linear-gradient(to bottom,
tone(-47%),
tone(-50%) 15%,
tone(-50%) 75%,
tone(-53%)
);
&:hover, &:focus {
@extend %glow;
outline: none;
color: tone(-55%);
text-shadow: none;
background: linear-gradient(to bottom,
tone(-27%),
tone(-30%) 15%,
tone(-30%) 75%,
tone(-35%)
);
}
&:active {
color: black;
padding-bottom: calc(0.1in - 2px);
padding-top: calc(0.1in + 2px);
background: linear-gradient(to top,
tone(-25%),
tone(-30%) 15%,
tone(-30%) 75%,
tone(-35%)
);
}
}
button { @extend %button;
&:first-of-type {
@extend %button;
color: white;
box-shadow: inset 0 1px tone(-25%),
inset 0 -1px tone(-50%);
background: linear-gradient(to bottom,
tone(-35%),
tone(-40%) 15%,
tone(-40%) 75%,
tone(-45%)
);
&:hover, &:focus {
box-shadow: inset 0 1px tone(-15%),
inset 0 -1px tone(-40%);
}
&:active {
box-shadow: inset 0 1px tone(-50%),
inset 0 -1px tone(-25%);
background: linear-gradient(to top,
tone(-30%),
tone(-35%) 15%,
tone(-35%) 75%,
tone(-40%)
);
}
}
&:hover { font-weight: bold; }
}
$grad-ui-focus: linear-gradient(to bottom,
tone(-50%),
tone(-35%)
);
input[type='text'], input[type='password'], textarea, select {
@extend %serif;
padding: 0.08in 0.1in;
box-sizing: border-box;
border: 1px solid black;
background: linear-gradient(to bottom, tone(-55%), tone(-40%));
font-size: 16pt;
color: tone(25%);
................................................................................
padding-bottom: 0.1in;
background-color: tone(-45%,-0.3);
border: {
left: 1px solid black;
right: 1px solid black;
}
}
div.profile {
padding: 0.1in;
position: relative;
display: grid;
margin-bottom: 0.4in;
grid-template-columns: 2fr 1fr;
................................................................................
grid-column: 1 / 2;
grid-row: 1 / 3;
border: 1px solid black;
}
> .id {
grid-column: 2 / 3;
grid-row: 1 / 2;
color: tone(25%,-0.4);
> .nym {
font-weight: bold;
color: tone(25%);
}
> .xid {
color: tone(20%,-0.1);
font-size: 80%;
vertical-align: text-top;
}
}
> .bio {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
}
> .stats {
grid-column: 3 / 4;
grid-row: 1 / 3;
}
> form.actions {
grid-column: 1 / 3; grid-row: 2 / 3;
padding-top: 0.075in;
flex-wrap: wrap;
display: flex;
justify-content: center;
................................................................................
}
input, textarea, .txtbox {
display: block;
width: 100%;
}
textarea { resize: vertical; min-height: 2in; }
}
.elem + %button { margin-left: 50%; width: 50%; }
}
menu.choice {
display: flex;
&.horizontal {
flex-flow: row-reverse wrap;
justify-content: space-evenly;
................................................................................
.color-picker {
/* implemented using javascript, alas */
@extend %box;
label { text-shadow: 1px 1px black; }
padding: 0.1in;
}
|
| | > | < | > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > < < < < < < < < < < > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .. 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 .. 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 ... 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 ... 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 ... 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 ... 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 |
$default-color: hsl(323,100%,65%);
%sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; }
%serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; }
%teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; }
// @function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) }
@function tone($pct, $alpha: 0) { @return hsla(var(--hue), 100%, 65% + $pct, 1 + $alpha) }
@function vtone($pct, $vary, $alpha: 0) { @return hsla(calc(var(--hue) + $vary), 100%, 65% + $pct, 1 + $alpha) }
@function otone($pct, $alpha: 0) { @return hsla(calc(var(--hue) + var(--co)), 100%, 65% + $pct, 1 + $alpha) }
:root { --hue: 323; --co: 0; }
body {
@extend %sans;
background-color: tone(-55%);
color: tone(25%);
font-size: 14pt;
margin: 0;
padding: 0;
................................................................................
::placeholder {
color: tone(0,-0.3);
font-style: italic;
}
a[href] {
color: tone(10%);
text-decoration-color: tone(10%,-0.5);
text-decoration-skip-ink: all;
text-decoration-thickness: 1px;
text-underline-offset: 0.1em;
&:hover, &:focus {
color: white;
text-shadow: 0 0 15px tone(20%);
text-decoration-color: tone(10%,-0.1);
outline: none;
}
&.button { @extend %button; }
................................................................................
%button {
@extend %sans;
font-size: 14pt;
box-sizing: border-box;
padding: 0.1in 0.2in;
border: 1px solid black;
color: otone(25%);
text-shadow: 1px 1px black;
text-decoration: none;
text-align: center;
cursor: default;
user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
background: linear-gradient(to bottom,
otone(-47%),
otone(-50%) 15%,
otone(-50%) 75%,
otone(-53%)
);
&:hover, &:focus {
@extend %glow;
outline: none;
color: tone(-55%);
text-shadow: none;
background: linear-gradient(to bottom,
otone(-27%),
otone(-30%) 15%,
otone(-30%) 75%,
otone(-35%)
);
}
&:active {
color: black;
padding-bottom: calc(0.1in - 2px);
padding-top: calc(0.1in + 2px);
background: linear-gradient(to top,
otone(-25%),
otone(-30%) 15%,
otone(-30%) 75%,
otone(-35%)
);
}
}
button { @extend %button;
&:first-of-type {
@extend %button;
color: white;
box-shadow: inset 0 1px otone(-25%),
inset 0 -1px otone(-50%);
background: linear-gradient(to bottom,
otone(-35%),
otone(-40%) 15%,
otone(-40%) 75%,
otone(-45%)
);
&:hover, &:focus {
box-shadow: inset 0 1px otone(-15%),
inset 0 -1px otone(-40%);
}
&:active {
box-shadow: inset 0 1px otone(-50%),
inset 0 -1px otone(-25%);
background: linear-gradient(to top,
otone(-30%),
otone(-35%) 15%,
otone(-35%) 75%,
otone(-40%)
);
}
}
//&:hover { font-weight: bold; }
}
$grad-ui-focus: linear-gradient(to bottom,
tone(-50%),
tone(-35%)
);
input[type='text'], input[type='number'], input[type='password'], textarea, select {
@extend %serif;
padding: 0.08in 0.1in;
box-sizing: border-box;
border: 1px solid black;
background: linear-gradient(to bottom, tone(-55%), tone(-40%));
font-size: 16pt;
color: tone(25%);
................................................................................
padding-bottom: 0.1in;
background-color: tone(-45%,-0.3);
border: {
left: 1px solid black;
right: 1px solid black;
}
}
.id {
color: tone(25%,-0.4);
> .nym {
font-weight: bold;
color: tone(25%);
}
> .xid {
color: tone(20%,-0.1);
font-size: 80%;
vertical-align: text-top;
}
}
div.profile {
padding: 0.1in;
position: relative;
display: grid;
margin-bottom: 0.4in;
grid-template-columns: 2fr 1fr;
................................................................................
grid-column: 1 / 2;
grid-row: 1 / 3;
border: 1px solid black;
}
> .id {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
> .bio {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
}
> .stats {
grid-column: 3 / 4;
grid-row: 1 / 3;
display: flex;
flex-flow: column;
> * { flex-grow: 1; }
table { td, th { text-align: center; } }
}
> form.actions {
grid-column: 1 / 3; grid-row: 2 / 3;
padding-top: 0.075in;
flex-wrap: wrap;
display: flex;
justify-content: center;
................................................................................
}
input, textarea, .txtbox {
display: block;
width: 100%;
}
textarea { resize: vertical; min-height: 2in; }
}
:is(.elem,.elem-group) + %button { margin-left: 50%; width: 50%; }
.elem-group {
display: flex;
flex-flow: row;
> .elem {
flex-shrink: 1;
flex-grow: 1;
margin-left: 0.1in;
&:first-child { margin-left: 0; }
}
> .small { flex-shrink: 5; }
}
}
menu.choice {
display: flex;
&.horizontal {
flex-flow: row-reverse wrap;
justify-content: space-evenly;
................................................................................
.color-picker {
/* implemented using javascript, alas */
@extend %box;
label { text-shadow: 1px 1px black; }
padding: 0.1in;
}
ul.user-list {
list-style-type: none;
margin: 0.5em 0;
padding: 0;
box-shadow: 0 0 10px -3px black inset;
border: 1px solid tone(-50%);
li {
background-color: tone(-20%, -0.8);
padding: 0.5em;
.regalia { margin-right: 0.3em; vertical-align: bottom; }
&:nth-child(odd) {
background-color: tone(-30%, -0.8);
}
}
}
ul.remarks {
margin: 0; padding: 0;
list-style-type: none;
li {
border-top: 1px solid otone(-22%);
border-bottom: 2px solid otone(-55%);
border-radius: 3px;
background: otone(-25%,-0.4);
color: otone(25%);
text-align: center;
padding: 0.3em 0;
margin: 0.2em 0.1em;
cursor: default;
}
}
:is(%button, a[href]).neg { --co: 60 }
:is(%button, a[href]).pos { --co: -30 }
|
Modified store.t from [e7b33e5534] to [da1c9184b0].
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 .. 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 .. 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 .. 94 95 96 97 98 99 100 101 102 103 104 105 106 107 ... 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 ... 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 ... 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 ... 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
'pw', 'otp', 'challenge', 'trust'
};
privset = lib.set {
'post', 'edit', 'acct', 'upload', 'censor', 'admin', 'invite'
};
powerset = lib.set {
-- user powers -- default on
'login', 'visible', 'post', 'shout',
'propagate', 'upload', 'acct', 'edit';
-- admin powers -- default off
'purge', 'config', 'censor', 'suspend',
'cred', 'elevate', 'demote', 'rebrand', -- modify site's brand identity
'herald', -- grant serverwide epithets
'invite' -- *unlimited* invites
};
prepmode = lib.enum {
'full','conf','admin'
}
}
................................................................................
m.privmap[#m.privmap + 1] = quote
var ps: m.powerset ps:clear()
(ps.[v] << true)
in pt {name = lib.str.plit(v), priv = ps} end
end end
terra m.powerset:affect_users()
return self.purge() or self.censor() or self.suspend() or
self.elevate() or self.demote() or self.cred()
end
local str = rawstring
local pstr = lib.mem.ptr(int8)
struct m.source
................................................................................
-- creating staff automatically assigns rank immediately below you
quota: uint32 -- # of allowed tweets per day; 0 = no limit
invites: uint32 -- # of people left this user can invite
powers: m.powerset
}
terra m.rights_default()
var pow: m.powerset pow:clear()
(pow.login << true)
(pow.visible << true)
(pow.post << true)
(pow.shout << true)
(pow.propagate << true)
(pow.upload << true)
(pow.acct << true)
(pow.edit << true)
return m.rights { rank = 0, quota = 1000, invites = 0, powers = pow; }
end
struct m.actor {
id: uint64
................................................................................
rights: m.rights
key: lib.mem.ptr(uint8)
-- ephemera
xid: str
source: &m.source
}
terra m.actor.methods.mk(kbuf: &uint8)
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,kbuf)
return m.actor {
id = 0; nym = nil; handle = nil;
origin = 0; bio = nil; avatar = nil;
................................................................................
conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
conf_set: {&m.source, rawstring, rawstring} -> {}
conf_reset: {&m.source, rawstring} -> {}
actor_create: {&m.source, &m.actor} -> uint64
actor_save: {&m.source, &m.actor} -> {}
actor_save_privs: {&m.source, &m.actor} -> {}
actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)
actor_stats: {&m.source, uint64} -> m.actor_stats
actor_rel: {&m.source, uint64, uint64} -> m.relationship
actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool}
-- returns a set of auth method categories that are available for a
-- given user from a certain origin
-- origin: inet
................................................................................
-- cookie issue time: m.timepoint
actor_auth_register_uid: {&m.source, uint64, uint64} -> {}
-- notifies the backend module of the UID that has been assigned for
-- an authentication ID
-- aid: uint64
-- uid: uint64
auth_enum_uid: {&m.source, uint64} -> lib.mem.ptr(lib.mem.ptr(m.auth))
auth_enum_handle: {&m.source, rawstring} -> lib.mem.ptr(lib.mem.ptr(m.auth))
auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
-- comment: pstring
auth_purge_pw: {&m.source, uint64, rawstring} -> {}
auth_purge_otp: {&m.source, uint64, rawstring} -> {}
................................................................................
-- 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_enum_parent: {&m.source, uint64} -> 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
thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint
................................................................................
-- proto: kompromat (null for all records, or a prototype describing the records to return)
nkvd_sanction_issue: {&m.source, &m.sanction} -> uint64
nkvd_sanction_vacate: {&m.source, uint64} -> {}
nkvd_sanction_enum_target: {&m.source, uint64} -> {}
nkvd_sanction_enum_issuer: {&m.source, uint64} -> {}
nkvd_sanction_review: {&m.source, m.timepoint} -> {}
timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
timeline_instance_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
}
m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8))
m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool)
struct m.source {
backend: &m.backend
|
| | > > > > > > < > > > > > > > | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | |
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 .. 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 .. 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 ... 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 ... 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 ... 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 ... 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 ... 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 |
'pw', 'otp', 'challenge', 'trust'
};
privset = lib.set {
'post', 'edit', 'acct', 'upload', 'censor', 'admin', 'invite'
};
powerset = lib.set {
-- user powers -- default on
'login', -- not locked out
'visible', -- account & posts can be seen by others
'post', -- can do poasts
'shout', -- posts show up on local timeline
'propagate', -- posts are sent to other instances
'artifact', -- upload, claim, and manage artifacts
'acct', -- configure own account
'edit'; -- edit own poasts
-- admin powers -- default off
'purge', -- permanently delete users
'config', -- change daemon policy & config UI
'censor', -- dispose of badthink
'discipline', -- enforced timeouts, stripping badges and epithets, punitive actions that do not permanently deprive of powers; can remove own injunctions but not others'
'vacate', -- can remove others' injunctions, but not apply them
'cred', -- alter credentials
'elevate', 'demote', -- change user rank, give and take powers, including the ability to log in
'rebrand', -- modify site's brand identity
'herald', -- grant serverwide epithets and badges
'invite' -- *unlimited* invites
};
prepmode = lib.enum {
'full','conf','admin'
}
}
................................................................................
m.privmap[#m.privmap + 1] = quote
var ps: m.powerset ps:clear()
(ps.[v] << true)
in pt {name = lib.str.plit(v), priv = ps} end
end end
terra m.powerset:affect_users()
return self.purge() or self.discipline() or self.herald() or
self.elevate() or self.demote() or self.cred()
end
local str = rawstring
local pstr = lib.mem.ptr(int8)
struct m.source
................................................................................
-- creating staff automatically assigns rank immediately below you
quota: uint32 -- # of allowed tweets per day; 0 = no limit
invites: uint32 -- # of people left this user can invite
powers: m.powerset
}
terra m.rights_default() -- TODO make configurable
var pow: m.powerset pow:clear()
(pow.login << true)
(pow.visible << true)
(pow.post << true)
(pow.shout << true)
(pow.propagate << true)
(pow.artifact << true)
(pow.acct << true)
(pow.edit << true)
return m.rights { rank = 0, quota = 1000, invites = 0, powers = pow; }
end
struct m.actor {
id: uint64
................................................................................
rights: m.rights
key: lib.mem.ptr(uint8)
-- ephemera
xid: str
source: &m.source
}
terra m.actor:outranks(other: &m.actor)
-- this predicate determines where two users stand relative to
-- each other in the formal staff hierarchy. it is used in
-- authority calculations, but this function should only be
-- used directly in rendering code and by other predicates.
-- do not use it in authority calculation, as there are special
-- cases where formal rank does not fully determine a user's
-- capabilities (e.g. roots have the same rank, but can
-- exercise power over each other, unlike lower ranks)
if self.rights.rank == 0 then
-- peons never outrank anybody
return false
end
if other.rights.rank == 0 then
-- everybody outranks peons
return true
end
return self.rights.rank < other.rights.rank
-- rank 1 is the highest possible, rank 2 is second-highest, and so on
end
terra m.actor:overpowers(other: &m.actor)
-- this predicate determines whether one user may exercise their
-- powers over another user. it does not affect what those powers
-- actually are (for instance, you cannot revoke a power you do
-- not have, no matter how much you outrank someone)
if self.rights.rank == 1 and other.rights.rank == 1 then
-- special case: root users always overpower each other
-- otherwise, nobody could reset their passwords
-- (also dissuades people from giving root lightly)
return true
end
return self:outranks(other)
end
terra m.actor.methods.handle_validate(hnd: rawstring)
if hnd[0] == 0 then
return false
end
-- TODO validate fully
return true
end
terra m.actor.methods.mk(kbuf: &uint8)
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,kbuf)
return m.actor {
id = 0; nym = nil; handle = nil;
origin = 0; bio = nil; avatar = nil;
................................................................................
conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
conf_set: {&m.source, rawstring, rawstring} -> {}
conf_reset: {&m.source, rawstring} -> {}
actor_create: {&m.source, &m.actor} -> uint64
actor_save: {&m.source, &m.actor} -> {}
actor_save_privs: {&m.source, &m.actor} -> {}
actor_purge_uid: {&m.source, uint64} -> {}
actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
actor_enum: {&m.source} -> lib.mem.lstptr(m.actor)
actor_enum_local: {&m.source} -> lib.mem.lstptr(m.actor)
actor_stats: {&m.source, uint64} -> m.actor_stats
actor_rel: {&m.source, uint64, uint64} -> m.relationship
actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool}
-- returns a set of auth method categories that are available for a
-- given user from a certain origin
-- origin: inet
................................................................................
-- cookie issue time: m.timepoint
actor_auth_register_uid: {&m.source, uint64, uint64} -> {}
-- notifies the backend module of the UID that has been assigned for
-- an authentication ID
-- aid: uint64
-- uid: uint64
auth_enum_uid: {&m.source, uint64} -> lib.mem.lstptr(m.auth)
auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth)
auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
-- comment: pstring
auth_purge_pw: {&m.source, uint64, rawstring} -> {}
auth_purge_otp: {&m.source, uint64, rawstring} -> {}
................................................................................
-- 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.lstptr(m.post)
post_enum_parent: {&m.source, uint64} -> lib.mem.lstptr(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
thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint
................................................................................
-- proto: kompromat (null for all records, or a prototype describing the records to return)
nkvd_sanction_issue: {&m.source, &m.sanction} -> uint64
nkvd_sanction_vacate: {&m.source, uint64} -> {}
nkvd_sanction_enum_target: {&m.source, uint64} -> {}
nkvd_sanction_enum_issuer: {&m.source, uint64} -> {}
nkvd_sanction_review: {&m.source, m.timepoint} -> {}
timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
timeline_instance_fetch: {&m.source, m.range} -> lib.mem.lstptr(m.post)
}
m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8))
m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool)
struct m.source {
backend: &m.backend
|
Modified view/profile.tpl from [694fa2eec6] to [4ac5403896].
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<div class="banner"> <img class="avatar" src="@:avatar"> <div class="id">@nym</div> <div class="bio"> @bio </div> </div> <table class="stats"> <tr><th>posts</th> <td>@nposts</td></tr> <tr><th>following</th> <td>@nfollows</td></tr> <tr><th>followers</th> <td>@nfollowers</td></tr> <tr><th>mutuals</th> <td>@nmutuals</td></tr> <tr><th>@timephrase</th> <td>@tweetday</td></tr> </table> <form class="actions"> <a class="button" href="/@:xid">posts</a> <a class="button" href="/@:xid/arc">archive</a> <a class="button" href="/@:xid/media">media</a> <a class="button" href="/@:xid/social">associates</a> <hr> @auxbtn </form> </div> |
| > | > | | < | | > > |
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 |
<div class="banner"> <img class="avatar" src="@:avatar"> <div class="id">@nym</div> <div class="bio"> @bio </div> </div> <div class="stats"> <table> <tr><th>posts</th> <th>mutuals</th></tr> <tr><td>@nposts</td> <td>@nmutuals</td></tr> <tr><th>following</th> <th>followers</th></tr> <tr><td>@nfollows</td> <td>@nfollowers</td></tr> <tr><th>@timephrase</th> <td>@tweetday</td></tr> </table> <ul class="remarks">@remarks</ul> </div> <form class="actions"> <a class="button" href="/@:xid">posts</a> <a class="button" href="/@:xid/arc">archive</a> <a class="button" href="/@:xid/media">media</a> <a class="button" href="/@:xid/social">associates</a> <hr> @auxbtn </form> </div> |