-- vim: ft=terra
local m = {
timepoint = lib.osclock.time_t;
scope = lib.enum {
'public', 'private', 'local';
'personal', 'direct', 'circle';
};
notiftype = lib.enum {
'mention', 'like', 'rt', 'react'
};
relation = lib.set {
'silence', -- messages will not be accepted
'collapse', -- posts will be collapsed by default
'disemvowel', -- posts will be ritually humiliated, but shown
'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations
'follow',
'mute', -- posts will be completely hidden at all times
'block', -- no interactions will be permitted, but posts will remain visible
};
credset = lib.set {
'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 = {}
do local struct pt { name:lib.mem.ptr(int8), priv:m.powerset }
for k,v in pairs(m.powerset.members) do
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
struct m.rights {
rank: uint16 -- lower = more powerful except 0 = regular user
-- 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
nym: str
handle: str
origin: uint64
bio: str
epithet: str
avatar: str
avatarid: uint64
knownsince: m.timepoint
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;
knownsince = lib.osclock.time(nil);
rights = m.rights_default();
epithet = nil, key = [lib.mem.ptr(uint8)] {
ptr = &kbuf[0], ct = privsz
};
}
end
struct m.actor_stats {
posts: intptr
follows: intptr
followers: intptr
mutuals: intptr
}
struct m.range {
mode: uint8 -- 0 == I->I, 1 == T->I, 2 == I->T, 3 == T->T
union {
from_time: m.timepoint
from_idx: uint64
}
union {
to_time: m.timepoint
to_idx: uint64
}
}
terra m.range:matrix()
if self.mode == 0 then
return self.from_time,self.to_time,0,0
elseif self.mode == 1 then
return self.from_time,0,self.to_idx,0
elseif self.mode == 2 then
return 0,self.to_time,0,self.from_idx
elseif self.mode == 3 then
return 0,0,self.to_idx,self.from_idx
else lib.bail('invalid mode on timeline range!') end
end
struct m.post {
id: uint64
author: uint64
subject: str
body: str
acl: str
posted: m.timepoint
discovered: m.timepoint
edited: m.timepoint
chgcount: uint
mentions: lib.mem.ptr(uint64)
circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle
convoheaduri: str
parent: uint64
-- ephemera
localpost: bool
source: &m.source
-- save :: bool -> {} (defined in acl.t due to dep. hell)
}
local cnf = terralib.memoize(function(ty,rty)
rty = rty or ty
return struct {
enum: {&opaque, uint64, rawstring} -> intptr
get: {&opaque, uint64, rawstring} -> rty
set: {&opaque, uint64, rawstring, ty} -> {}
reset: {&opaque, uint64, rawstring} -> {}
}
end)
struct m.notif {
kind: m.notiftype.t
when: uint64
union {
post: uint64
reaction: int8[8]
}
}
struct m.inet {
pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6
union {
v4: uint8[4]
v6: uint8[16]
}
union {
fixbits: uint8 -- for cidr
port: uint16 -- for origin
}
}
terra m.inet:cidr_str()
if self.pv == 4 then
var maxsz = 3*4 + 3 + 1
elseif self.pv == 6 then
var bits = 128
var bytes = bits / 8
var hexchs = bytes * 2
var segs = hexchs / 4
var seps = segs - 1
var maxsz = hexchs + seps + 1
else return nil end
end
struct m.kompromat {
-- The Evidence
id: uint64
perp: uint64 -- whodunnit
desc: str
post: uint64 -- the post in question, if any
reporter: uint64 -- 0 = originated automatically by the System itself
resolution: str -- null for unresolved
-- as proto: set resolution to empty string to search for resolved incidents
}
struct m.sanction {
id: uint64
issuer: uint64
scope: uint64
nature: uint16
victim: uint64
autoexpire: bool expire: m.timepoint
timedreview: bool review: m.timepoint
reason: str
context: str
}
struct m.auth {
-- a credential record
aid: uint64
uid: uint64
aname: str
netmask: m.inet
privs: m.privset
blacklist: bool
}
struct m.relationship {
agent: uint64
patient: uint64
rel: m.relation -- agent → patient
recip: m.relation -- patient → agent
}
-- backends only handle content on the local server
struct m.backend { id: rawstring
open: &m.source -> &opaque
close: &m.source -> {}
dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`)
conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database
tx_enter: &m.source -> bool
tx_complete: &m.source -> bool
-- these two functions are special, in that they should be called
-- directly on a specific backend, rather than passed down to the
-- backends by the server; that is pathological behavior that will
-- not have the desired effect
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
-- username: rawstring
actor_auth_otp: {&m.source, m.inet, rawstring, rawstring}
-> {uint64, uint64, pstr}
actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) }
-> {uint64, uint64, pstr}
-- handles password-based logins against hashed passwords
-- origin: inet
-- handle: rawstring
-- token: rawstring
actor_auth_tls: {&m.source, m.inet, rawstring}
-> {uint64, uint64, pstr}
-- handles implicit authentication performed as part of an TLS connection
-- origin: inet
-- fingerprint: rawstring
actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64
-> {uint64, uint64, pstr}
-- handles API authentication
-- origin: inet
-- handle: rawstring
-- key: rawstring (X-API-Key)
actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth)
actor_powers_fetch: {&m.source, uint64} -> m.powerset
actor_session_fetch: {&m.source, uint64, m.inet, m.timepoint} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)}
-- retrieves an auth record + actor combo suitable by AID suitable
-- for determining session validity & caps
-- aid: uint64
-- 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
actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
actor_conf_int: cnf(intptr, lib.stat(intptr))
auth_create_pw: {&m.source, uint64, bool, lib.mem.ptr(int8)} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
auth_purge_pw: {&m.source, uint64, rawstring} -> {}
auth_purge_otp: {&m.source, uint64, rawstring} -> {}
auth_purge_trust: {&m.source, uint64, rawstring} -> {}
auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint
-- authentication tokens and accounts have a property that controls
-- whether auth cookies dated to a certain point are valid. cookies
-- that are generated before the timepoint are considered invalid.
-- this is used primarily to lock out untrusted sessions.
-- uid: uint64
auth_sigtime_user_alter: {&m.source, uint64, m.timepoint} -> {}
-- uid: uint64
-- timestamp: timepoint
post_save: {&m.source, &m.post} -> {}
post_create: {&m.source, &m.post} -> uint64
post_destroy: {&m.source, uint64} -> {}
post_fetch: {&m.source, uint64} -> lib.mem.ptr(m.post)
post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
post_attach_ctl: {&m.source, uint64, uint64, bool} -> {}
-- attaches or detaches an existing database artifact
-- post id: uint64
-- artifact id: uint64
-- detach: bool
artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64
-- instantiate an artifact in the database, either installing a new
-- artifact or returning the id of an existing artifact with the same hash
-- artifact: bytea
-- mime: pstring
artifact_quicksearch: {&m.source, lib.mem.ptr(uint8)} -> {uint64,bool}
-- checks whether a hash is already in the database without uploading
-- the entire file to the database server
-- hash: bytea
--> artifact id (0 if null), suppressed?
artifact_expropriate: {&m.source, uint64, uint64, lib.mem.ptr(int8)} -> {}
-- claims an existing artifact for the user's own collection
-- uid: uint64
-- artifact id: uint64
-- description: pstring
artifact_disclaim: {&m.source, uint64, uint64} -> {}
-- a user disclaims their ownership stake in an artifact, removing it from
-- the database entirely if they were the only owner, and removing their
-- description of it either way
-- uid: uint64
-- artifact id: uint64
artifact_excise: {&m.source, uint64, bool} -> {}
-- (admin action) forcibly excise an artifact from the database, deleting
-- all links to it and removing it from users' collections. if "blacklist,"
-- the artifact will be banned and attempts to upload it in the future
-- will fail, triggering a report. mainly intended for dealing with spam,
-- IP violations, That Which Shall Not Be Named, and various other infohazards.
-- artifact id: uint64
-- blacklist: bool
nkvd_report_issue: {&m.source, &m.kompromat} -> {}
-- an incidence of Badthink has been detected. report it immediately
-- to the Supreme Soviet
nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat)
-- search through the Archives
-- 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} -> {}
convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
convo_fetch_cid: {&m.source,uint64} -> lib.mem.ptr(m.post)
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))
}
struct m.source {
backend: &m.backend
id: lib.mem.ptr(int8)
handle: &opaque
string: lib.mem.ptr(int8)
}
terra m.source:free()
self.id:free()
self.string:free()
end
m.source.metamethods.__methodmissing = macro(function(meth, obj, ...)
local q = {...}
-- syntax sugar to forward unrecognized calls onto the backend
return quote var r = obj.backend.[meth](&obj, [q]) in r end
end)
return m