20from flask
import Response
34 """In order to enable caching for static pages, we place all session
35 and user specific information
in this special handler. Using this
36 technique, we can use nginx to rapidly serve static files
while this
37 function only has minimal work to do.
40P.wallpaper = '{g.session['wallpaper
']}';
41P.texttheme =
'{g.session['texttheme
']}';
42P.userid =
'{ToShortCode(g.session.userid) if g.session.userid else ''}';
43P.groups = {g.session[
'groups']
if g.session.userid
else []};
44P.lang =
'{g.session.lang}';
45P.langcodes = {g.session.langcodes};
46P.languages = {g.siteconfig[
'languages']};
48let chooselanguageicon = E(
'.ChooseLanguageIcon');
50if (chooselanguageicon)
52 chooselanguageicon.innerHTML = `<img
class=
"Square200" src=
"/files/flag-${{P.lang}}.png">`
55let systemusericon = E(
'.SystemUserIcon');
57if (systemusericon && P.userid)
59 systemusericon.innerHTML = `<img
class=
"Square200" src=
"/system/headshots/${{P.CodeDir(P.userid)}}.webp">`
63 r.headers["Cache-Control"] =
"no-cache, no-store, must-revalidate"
64 r.headers[
"Pragma"] =
"no-cache"
65 r.headers[
"Expires"] =
"0"
66 r.headers[
'Cache-Control'] =
'public, max-age=0'
72 """Standard API to search through the files on the system and change
73 names or descriptions. Note that files are one of the few objects
74 where security
is enabled by default, so we rely on the system
75 security protection to block unauthorized access.
80 d = json.loads(g.request.get_data())
82 command =
StrV(d,
'command')
84 if command ==
'search':
85 keyword =
StrV(d,
'keyword')
86 filename =
StrV(d,
'filename', keyword)
87 filetype =
StrV(d,
'filetype')
88 modified =
StrV(d,
'modified')
89 size =
IntV(d,
'size')
90 start =
IntV(d,
'start')
91 limit =
IntV(d,
'limit', 1, 1000, 20)
92 orderby =
StrV(d,
'orderby',
'filename')
93 filefilter =
StrV(d,
'filter')
94 fileids =
ArrayV(d,
'fileids')
95 showallfiles =
IntV(d,
'showallfiles')
101 conds.append(
'dbowner = ?')
102 params.append(g.session.userid)
105 conds.append(
'rid IN (' + [
', '.join([
'?' for x
in fileids])] +
')')
109 conds.append(
"filename LIKE ? OR descriptions LIKE ?")
110 params.append(
EscapeSQL(
'%' + filename +
'%'))
111 params.append(
EscapeSQL(
'%' + filename +
'%'))
114 if filetype
in system_file.TYPES.keys():
115 filetype = system_file.TYPES[filetype]
116 conds.append(
"filetype = ?")
117 params.append(filetype)
120 conds.append(
"modified LIKE ?")
121 params.append(
EscapeSQL(
'%' + modified +
'%'))
125 conds.append(
"size > ?")
129 conds =
"WHERE " + (
" AND ").join(conds)
138 fields =
'rid, filename, extension, descriptions, modified, size, filetype, flags',
144 o = r.ToJSON(
'filename, extension, modified, size, flags')
145 o[
'modified'] = o[
'modified'][:19]
146 o[
'description'] =
BestTranslation(r.descriptions, g.session.langcodes)
148 o[
'thumbnail'] = r.ThumbnailURL()
152 results[
'count'] = len(files)
153 elif command ==
'load':
154 idcode =
StrV(d,
'idcode')
157 raise Exception(g.T.missingparams)
161 o = r.ToJSON(
'filename, modified, size, flags')
163 o[
'modified'] = o[
'modified'][:19]
164 o[
'description'] =
BestTranslation(r.descriptions, g.session.langcodes)
166 o[
'thumbnail'] = r.ThumbnailURL()
169 elif command ==
'save':
170 """The save API is only able to change filename and descriptions. The
171 rest of the statistics are automatically handled on upload.
173 newinfo = DictV(d, 'data')
175 if not V(newinfo,
'idcode'):
176 raise Exception(g.T.cantcreate)
180 filename =
StrV(newinfo,
'filename')
181 descriptions =
DictV(newinfo,
'descriptions')
183 if filename
or descriptions:
185 s.Set(filename = filename)
188 s.Set(descriptions = descriptions)
193 elif command ==
'delete':
197 raise Exception(g.T.missingparams)
199 for r
in g.db.LoadMany(system_file, [
FromShortCode(x)
for x
in ids]):
200 filepath = r.FullPath()
208 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
209 except Exception
as e:
210 traceback.print_exc()
211 results[
'error'] = str(e)
217 """Handles uploads for the whole system. Files in Pafera are stored
218 under their shortcodes, and can be private
or public. Note that you
'll
219 need ImageMagick and ffmpegthumbnailer installed on your system
for
220 automatic thumbnail generation.
223 if len(g.urlargs) < 3:
225 errordict[g.session.lang] = g.T.missingparams
226 return json.dumps(errordict)
228 fileid, filename, title = g.urlargs[:3]
232 if len(g.urlargs) > 3:
233 security = g.urlargs[3]
238 g.db.RequireGroup(
'uploaders', g.T.cantchange)
240 bytesleft = int(g.request.headers.get(
'content-length'))
244 if fileid ==
'headshot':
247 if (userid != g.session.userid
248 and not g.db.HasLink(system_user, g.session.userid, system_user, userid, USER_LINK_MANAGE)
250 return json.dumps({
'error': g.T.cantchange})
252 basename =
'static/system/headshots/' +
CodeDir(filename)
253 jpgfile = basename +
'.jpg'
254 webpfile = basename +
'.webp'
256 with open(jpgfile,
'wb')
as f:
258 chunk = g.request.stream.read(65536)
260 bytesleft -= len(chunk)
262 subprocess.call([
'convert', jpgfile,
'-quality',
'70',
'-resize',
'512x512>', webpfile])
268 cachedir =
'/srv/flasksite/private/uploads'
269 uploadedfile = cachedir +
'/' + uuid.uuid4().hex
271 hasher = hashlib.sha1()
273 with open(uploadedfile,
'wb')
as f:
275 chunk = g.request.stream.read(65536)
278 bytesleft -= len(chunk)
280 finalhash = hasher.hexdigest()
285 'WHERE hash = ? AND size = ? AND NOT flags & ?',
286 (hash, filesize, FILE_PRIVATE)
291 os.remove(uploadedfile)
294 basename, extension = os.path.splitext(filename)
298 descriptions[g.session.lang] = title
304 extension = extension,
305 descriptions = descriptions,
309 if security ==
'protected':
310 f.Set(flags = FILE_PROTECTED)
311 elif security ==
'private':
312 f.Set(flags = FILE_PRIVATE)
314 f.Set(flags = FILE_PUBLIC)
319 fullpath =
'static/data/files' +
CodeDir(idcode) +
'.' + extension
321 os.rename(uploadedfile, fullpath)
329 'thumbnail': f.ThumbnailURL(),
332 return json.dumps(returninfo)
336 """Handles logins, registrations, approvals, and user information.
337 Also handles adding friends, following other users, and so forth.
339 Note that normal users cannot search
for other users. You must be
340 an administrator
or have managed users assigned to you
in order to
341 see
and change other users.
348 d = json.loads(g.request.get_data())
350 command =
StrV(d,
'command')
352 if command ==
'login':
353 phonenumber =
StrV(d,
'phonenumber').strip()
354 place =
StrV(d,
'place').strip()
355 password =
StrV(d,
'password').strip()
357 if not phonenumber
or not place
or not password:
358 raise Exception(g.T.missingparams)
362 previousattempt = g.db.Find(
364 'WHERE ip = ? ORDER BY eventtime DESC',
368 currenttime = time.time()
370 if previousattempt
and previousattempt[0].eventtime.timestamp() > currenttime - 2:
372 raise Exception(g.T.nothingfound)
374 loginattempt = system_loginattempt()
377 eventtime = currenttime,
379 g.db.Insert(loginattempt)
384 'WHERE phonenumber = ? AND place = ?',
390 if u.CheckPassword(
'password', password):
394 g.session.data.update(u.settings)
399 for group
in g.db.Linked(u, system_group):
400 groups.append(group.groupname)
401 groupids.append(group.rid)
403 g.session[
'groups'] = groups
404 g.session[
'groupids'] = groupids
406 g.session.needupdate = 1
409 raise Exception(g.T.nothingfound)
412 raise Exception(g.T.nothingfound)
413 elif command ==
'register':
414 phonenumber =
StrV(d,
'phonenumber').strip()
415 place =
StrV(d,
'place').strip()
416 password =
StrV(d,
'password').strip()
417 password2 =
StrV(d,
'password2').strip()
418 email =
StrV(d,
'email').strip()
419 headshot =
StrV(d,
'headshot').strip()
420 extrainfo =
DictV(d,
'extrainfo')
422 if not phonenumber
or not place
or not password
or not password2
or not headshot
or len(headshot) < 60:
423 raise Exception(g.T.missingparams)
427 'WHERE phonenumber = ? AND place = ?',
431 if u
and g.session.userid != u[0].rid:
432 raise Exception(g.T.phonenumberalreadytaken)
437 raise Exception(g.T.wrongfileformat)
445 phonenumber = phonenumber,
448 extrainfo = extrainfo
451 u.SetPassword(
'password', password)
456 headshotdir =
'static/system/newheadshots/' + idcode[:3]
458 os.makedirs(headshotdir, exist_ok =
True)
460 with open(headshotdir +
'/' + idcode[3:] +
'.webp',
'wb')
as f:
464 elif command ==
'listneedapprovals':
465 if 'admins' not in g.session[
'groups']:
466 raise Exception(g.T.cantview)
468 start =
IntV(d,
'start', 0, 99999999, 0)
469 limit =
IntV(d,
'limit', 10, 1000, 100)
477 orderby =
'place, phonenumber'
480 elif command ==
'approve':
481 if 'admins' not in g.session[
'groups']:
482 raise Exception(g.T.cantchange)
484 userid =
StrV(d,
'userid')
485 approve =
IntV(d,
'approve')
489 u = g.db.Load(system_newuser, userid)
493 user.Set(**u.ToJSON())
501 os.makedirs(os.path.split(userimage)[0], exist_ok =
True)
503 os.rename(newuserimage, userimage)
509 elif command ==
'search':
510 keyword =
StrV(d,
'keyword')
511 start =
IntV(d,
'start', 0, 2147483647, 0)
512 limit =
IntV(d,
'limit', 1, 2147483647, 100)
518 conds.append(
'phonenumber LIKE ?')
519 params.append(
EscapeSQL(
'%' + keyword +
'%'))
521 conds.append(
'displayname LIKE ?')
522 params.append(
EscapeSQL(
'%' + keyword +
'%'))
525 conds =
' OR '.join(conds)
529 currentuser = g.db.Load(system_user, g.session.userid,
'rid, canmanage, managedby')
531 userids =
ArrayV(d,
'userids')
537 if r
in currentuser.canmanage
or r
in currentuser.managedby:
538 validuserids.append(r)
543 cond =
'WHERE rid IN (' + (
', '.join([
'?' for x
in validuserids])) +
') '
546 cond +=
' AND (' + conds +
')'
550 validusers = g.db.Find(
556 orderby =
'phonenumber',
557 fields =
'rid, phonenumber, place, displayname'
561 x.ToJSON(
'rid, phonenumber, place, displayname')
564 results[
'count'] = len(validusers)
569 if 'admins' in g.session[
'groups']:
570 allusers = g.db.Find(
572 'WHERE ' + conds
if conds
else '',
576 orderby =
'phonenumber',
579 results[
'allusers'] = [
580 x.ToJSON(
'rid, phonenumber, place, displayname')
583 results[
'alluserscount'] = len(allusers)
585 if currentuser.canmanage:
586 cond =
'WHERE rid IN (' + (
', '.join([
'?' for x
in currentuser.canmanage])) +
') '
589 cond +=
' AND (' + conds +
')'
591 params = [
FromShortCode(x)
for x
in currentuser.canmanage] + params
593 managedusers = g.db.Find(
599 orderby =
'phonenumber',
600 fields =
'rid, phonenumber, place, displayname, expiredate, storagequota, numcanmanage, canmanage, managedby',
606 x.ToJSON(
'rid, phonenumber, place, displayname, expiredate, storagequota, numcanmanage, canmanage, managedby')
607 for x
in managedusers
609 results[
'count'] = len(managedusers)
610 elif command ==
'save':
611 data =
DictV(d,
'data')
612 userid =
StrV(data,
'idcode')
615 raise Exception(g.T.missingparams)
620 u = g.db.Load(system_user, userid,
'rid, numcanmanage, canmanage, managedby, flags')
622 isadmin =
'admins' in g.session[
'groups']
623 canmanage = isadmin
or ToShortCode(g.session.userid)
in u.managedby
625 if userid != g.session.userid
and not canmanage:
626 raise Exception(g.T.cantchange)
632 if not isadmin
and userid == g.session.userid
and not u.flags & USER_CAN_MANAGE_SELF:
633 newpassword =
StrV(data,
'password')
639 'password': newpassword
649 value =
StrV(data, r)
655 expiredate =
StrV(data,
'expiredate')
658 newvalues[
'expiredate'] = expiredate
660 storagequota =
IntV(data,
'storagequota')
663 newvalues[
'storagequota'] = storagequota
665 numcanmanage =
IntV(data,
'numcanmanage')
668 newvalues[
'numcanmanage'] = numcanmanage
671 canmanagetodelete = []
674 managedbytodelete = []
676 canmanage =
ArrayV(data,
'canmanage')
678 if 'canmanage' in data:
679 oldcanmanage = set(u.canmanage)
680 newcanmanage = set(canmanage)
682 newvalues[
'canmanage'] = list(newcanmanage)
684 canmanagetoadd = list(newcanmanage - oldcanmanage)
685 canmanagetodelete = list(oldcanmanage - newcanmanage)
687 if not isadmin
and len(canmanage) > u.numcanmanage:
688 raise Exception(g.T.reachedmanagelimit)
690 managedby =
ArrayV(data,
'managedby')
692 if 'managedby' in data:
693 newvalues[
'managedby'] = managedby
695 oldmanagedby = set(u.managedby)
696 newmanagedby = set(managedby)
698 managedbytoadd = list(newmanagedby - oldmanagedby)
699 managedbytodelete = list(oldmanagedby - newmanagedby)
701 userstoedit = canmanagetoadd + canmanagetodelete + managedbytoadd + managedbytodelete
706 'WHERE rid IN (' +
', '.join([
'?' for x
in userstoedit]) +
')',
708 fields =
'rid, canmanage, managedby'
713 if thisuserid
in canmanagetoadd:
714 r.managedby.append(useridcode)
715 r.UpdateFields(
'managedby')
717 if thisuserid
in canmanagetodelete
and useridcode
in r.managedby:
718 r.managedby.remove(useridcode)
719 r.UpdateFields(
'managedby')
721 if thisuserid
in managedbytoadd:
722 r.canmanage.append(useridcode)
723 r.UpdateFields(
'canmanage')
725 if thisuserid
in managedbytodelete
and useridcode
in r.canmanage:
726 r.canmanage.remove(useridcode)
727 r.UpdateFields(
'canmanage')
733 if 'password' in data:
734 u.SetPassword(
'password', data[
'password'])
738 elif command ==
'delete':
742 raise Exception(g.T.missingparams)
747 u = g.db.Load(system_user, userid,
'rid, managedby')
749 if 'admins' not in g.session[
'groups']
and ToShortCode(g.session.userid)
not in u.managedby:
750 raise Exception(g.T.cantdelete)
755 elif command ==
'load':
756 userid =
StrV(d,
'userid')
761 userid = g.session.userid
763 u = g.db.Load(system_user, g.session.userid,
'rid, phonenumber, place, displayname, storageused, storagequota, numcanmanage, canmanage, managedby, accesstokens, flags')
765 canmanage =
'admins' in g.session[
'groups']
or ToShortCode(g.session.userid)
in u.managedby
767 if userid != g.session.userid
and not canmanage:
768 raise Exception(g.T.cantchange)
770 results[
'data'] = u.ToJSON(
'rid, phonenumber, place, displayname, storageused, storagequota, numcanmanage, canmanage, managedby, accesstokens, flags')
771 elif command ==
'setlanguage':
772 newlang =
StrV(d,
'data')
774 if newlang
not in g.siteconfig[
'languages']:
775 raise Exception(newlang +
' is not supported.')
777 newlangcodes = [newlang]
779 [newlangcodes.append(x)
for x
in g.session.langcodes
if x
not in newlangcodes]
783 langcodes = newlangcodes,
786 g.db.Update(g.session)
788 elif command ==
'searchgroups':
789 keyword =
StrV(d,
'keyword')
790 grouptype =
StrV(d,
'grouptype',
'friends')
791 start =
IntV(d,
'start', 0, 99999999, 0)
792 limit =
IntV(d,
'limit', 10, 99999999, 100)
798 conds.append(
'(phonenumber LIKE ? OR displayname LIKE ?)')
799 params.append(
EscapeSQL(
'%' + keyword +
'%'))
800 params.append(
EscapeSQL(
'%' + keyword +
'%'))
803 cond =
'WHERE ' +
' AND '.join(conds)
808 if 'admins' in g.session[
'groups']:
815 fields =
'rid, displayname, phonenumber'
818 linktype = USER_FRIEND
820 if grouptype ==
'friends':
822 elif grouptype ==
'acquaintances':
823 linktype = USER_ACQUAINTANCE
824 elif grouptype ==
'follow':
825 linktype = USER_FOLLOW
826 elif grouptype ==
'blocked':
827 linktype = USER_BLOCKED
829 raise Exception(g.T.cantunderstand + grouptype)
831 users = g.db.LinkedToID(
834 fields =
'rid, displayname, phonenumber'
837 results[
'count'] = len(users)
843 'phonenumber': r.phonenumber
846 results[
'data'] = objs
848 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
849 except Exception
as e:
850 traceback.print_exc()
851 results[
'error'] = str(e)
857 """Configures settings for the whole site. You must be an
858 administrator in order to see
and change settings.
863 if 'admins' not in g.session[
'groups']:
864 raise Exception(g.T.cantchange)
866 d = json.loads(g.request.get_data())
868 command =
StrV(d,
'command')
870 if command ==
'getinfo':
871 with open(
'private/system/pafera.cfg',
'r')
as f:
872 results[
'data'] = json.load(f)
873 elif command ==
'setinfo':
875 newinfo =
DictV(d,
'data')
877 if not V(newinfo,
'dbflags'):
878 newinfo[
'dbflags'] = 0
880 with open(
'private/system/pafera.cfg',
'r')
as f:
881 oldinfo = json.load(f)
885 connectionname =
'newdb',
886 dbhost = newinfo[
'dbhost'],
887 dbname = newinfo[
'dbname'],
888 dbtype = newinfo[
'dbtype'],
889 dbuser = newinfo[
'dbuser'],
890 dbpassword = newinfo[
'dbpassword'],
891 dbflags = newinfo[
'dbflags'],
895 newg = types.SimpleNamespace()
899 except Exception
as e:
900 traceback.print_exc()
901 raise Exception(g.T.cantconnect + newinfo[
'dbname'])
905 'WHERE phonenumber = ? AND place = ?',
909 sitepassword =
StrV(newinfo,
'sitepassword')
911 if not sitepassword
and admin.CheckPassword(
'password',
'pafera'):
912 raise Exception(g.T.pleasesetnewpassword)
914 admin.SetPassword(
'password', sitepassword)
917 newinfo.pop(
'sitepassword')
919 oldinfo.update(newinfo)
921 newinfo[
'sessiontimeout'] =
IntV(newinfo,
'sessiontimeout', 600, 2678400, 604800)
923 with open(
'private/system/pafera.cfg',
'w')
as f:
924 f.write(json.dumps(oldinfo, sort_keys =
True, indent = 2))
926 if newinfo[
'production']:
927 subprocess.run([
'/srv/flasksite/scripts/minifyall'])
929 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
930 except Exception
as e:
931 results[
'error'] = str(e)
937 """Used by the web database management interface to handle objects
938 in the system. You must be an administrator to use this API.
943 if 'admins' not in g.session[
'groups']:
944 raise Exception(g.T.cantchange)
946 d = json.loads(g.request.get_data())
948 command =
StrV(d,
'command')
950 if command ==
'getmodels':
951 results[
'data'] = list(g.db.objtypes.keys())
952 elif command ==
'getmodelinfo':
956 raise Exception(g.T.missingparams)
958 g.db.RegisterModel(model)
963 for k, v
in model._dbfields.items():
966 results[
'data'] = data
967 results[
'modelid'] = model._dbid
968 results[
'managejs'] = getattr(model,
'_managejs',
'')
969 results[
'links'] = getattr(model,
'_dblinks', [])
970 results[
'displayfields'] = getattr(model,
'_dbdisplay', model._dbid)
971 elif command ==
'search':
973 fields =
ArrayV(d,
'fields')
974 cond =
StrV(d,
'condition')
975 params =
ArrayV(d,
'params')
976 start =
IntV(d,
'start', 0, 99999999, 0)
977 limit =
IntV(d,
'limit', 10, 99999999, 100)
978 orderby =
StrV(d,
'orderby')
981 raise Exception(g.T.missingparams)
986 g.db.RegisterModel(model)
994 fields =
', '.join(fields)
if fields
else '*',
998 results[
'count'] = len(objs)
1000 fieldnames =
', '.join(fields)
1002 results[
'data'] = [x.ToJSON(fieldnames)
for x
in objs]
1003 elif command ==
'save':
1005 data =
DictV(d,
'data')
1007 if not model
or not data:
1008 raise Exception(g.T.missingparams)
1010 g.db.RegisterModel(model)
1012 dbid =
V(data, model._dbid)
1015 raise Exception(g.T.missingparams)
1019 if 'INT' in model._dbfields[model._dbid][0]:
1023 obj = g.db.Load(model, dbid)
1024 except Exception
as e:
1025 print(
'dbapi.save():\tCould not load model ', model, dbid)
1030 for k, v
in model._dbfields.items():
1031 if v[0] ==
'PASSWORD':
1033 obj.SetPassword(k, data[k])
1036 elif v[0] ==
'JSON':
1046 results[
'data'] = obj.ToJSON()
1047 elif command ==
'load':
1049 modelid =
V(d,
'modelid')
1051 if not model
or not modelid:
1052 raise Exception(g.T.missingparams)
1054 g.db.RegisterModel(model)
1056 obj = g.db.Load(model, modelid)
1058 results[
'data'] = obj.ToJSON()
1059 elif command ==
'delete':
1064 raise Exception(g.T.missingparams)
1066 g.db.RegisterModel(model)
1069 results[
'error'] =
'No IDs provided to delete.'
1073 raise Exception(g.T.missingparams)
1083 elif command ==
'linked':
1087 linktype =
IntV(d,
'type')
1089 if not model1
or not model2
or not id1:
1090 raise Exception(g.T.missingparams)
1092 g.db.RegisterModel(model1)
1093 g.db.RegisterModel(model2)
1095 obj1 = g.db.Load(model1, id1)
1097 displayfields = getattr(model2,
'_dbdisplay', [])
1100 displayfields = displayfields + [model2._dbid]
1102 results[
'data'] = [x.ToJSON(
', '.join(displayfields))
for x
in g.db.Linked(obj1, model2, linktype)]
1103 elif command ==
'link' or command ==
'linkarray':
1108 linktype =
IntV(d,
'type')
1110 if not model1
or not model2
or not id1:
1111 raise Exception(g.T.missingparams)
1113 g.db.RegisterModel(model1)
1114 g.db.RegisterModel(model2)
1116 obj1 = g.db.Load(model1, id1)
1121 ls.append(g.db.Load(model2, id))
1123 if len(ls) == 1
and command ==
'link':
1124 g.db.Link(obj1, ls[0], linktype)
1127 g.db.LinkArray(obj1, ls, linktype)
1129 g.db.Unlink(obj1, model2, linktype)
1132 elif command ==
'cleanlinks':
1133 """Look for broken links where the original objects don't exist
1134 anymore and clean them
from the system.
1141 for r
in list(g.db.objtypes.keys()):
1146 report.append(f
'Found model {r}')
1148 for l
in m1._dblinks:
1151 linktable = g.db.GetLinkTable(m1, m2)
1153 linkobj1, linkobj2 = linktable.split(
'__')
1156 linkobj1id = m1._dbid
1157 linkobj2id = m2._dbid
1159 linkobj1id = m2._dbid
1160 linkobj2id = m1._dbid
1162 report.append(f
'\tFound link {linktable}')
1164 if g.db.HasTable(linktable):
1167 for a
in g.db.Execute(f
'''
1168 SELECT lt.linkaid, lt.linkbid
1169 FROM {linktable} AS lt
1170 LEFT JOIN {linkobj1} AS l1 ON lt.linkaid = l1.{linkobj1id}
1171 WHERE l1.{linkobj1id} IS NULL
1173 report.append(f'''\t\tFound broken link {a[0]} -> {a[1]}''')
1174 brokenlinks.append((a[0], a[1]))
1175 numlinkscleaned += 1
1177 for a
in g.db.Execute(f
'''
1178 SELECT lt.linkaid, lt.linkbid
1179 FROM {linktable} AS lt
1180 LEFT JOIN {linkobj2} AS l2 ON lt.linkbid = l2.{linkobj2id}
1181 WHERE l2.{linkobj2id} IS NULL
1183 report.append(f'''\t\tFound broken link {a[0]} -> {a[1]}''')
1184 brokenlinks.append((a[0], a[1]))
1185 numlinkscleaned += 1
1188 g.db.ExecuteMany(f
'''
1189 DELETE FROM {linktable}
1190 WHERE linkaid = ? AND linkbid = ?
1197 report.append(f'''Cleaned {numlinkscleaned} broken links from {nummodels} models''')
1199 results[
'report'] =
'\n'.join(report)
1201 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
1202 except Exception
as e:
1203 traceback.print_exc()
1204 results[
'error'] = str(e)
1210 """Handles frequently used translations for the system. You must be
1211 an administrator or be part of the translators group
in order to use
1217 if (
'admins' not in g.session[
'groups']
1218 and 'translators' not in g.session[
'groups']
1220 raise Exception(g.T.cantchange)
1222 d = json.loads(g.request.get_data())
1224 command =
StrV(d,
'command')
1226 if command ==
'list':
1229 for f
in os.listdir(
'static'):
1230 apppath = os.path.join(
'static', f)
1232 if os.path.isdir(apppath):
1235 for l
in g.siteconfig[
'languages']:
1236 translationspath = os.path.join(apppath,
'translations', l)
1238 if os.path.exists(translationspath):
1239 for t
in os.listdir(translationspath):
1240 translations[os.path.splitext(t)[0]] = 1
1242 apps[f] = list(translations.keys())
1244 results[
'data'] = apps
1245 elif command ==
'load':
1246 app =
StrV(d,
'app')
1247 translation =
StrV(d,
'translation')
1248 languages =
ArrayV(d,
'languages')
1250 if not app
or not translation
or not languages:
1251 raise Exception(g.T.missingparams)
1255 for lang
in languages:
1257 T = types.SimpleNamespace()
1259 with open(
'static/' + app +
'/translations/' + lang +
'/' + translation +
'.js',
'r')
as f:
1262 translations[lang] = T.__dict__
1263 except Exception
as e:
1266 results[
'data'] = translations
1267 elif command ==
'save':
1268 app =
StrV(d,
'app')
1269 translation =
StrV(d,
'translation')
1270 data =
DictV(d,
'data')
1272 if not app
or not translation:
1273 raise Exception(g.T.missingparams)
1277 for l
in g.siteconfig[
'languages']:
1280 results[
'error'] =
'';
1282 for lang, translations
in data.items():
1284 dirname =
'static/' + app +
'/translations/' + lang
1286 os.makedirs(dirname, 0o777,
True)
1288 with open(dirname +
'/' + translation +
'.js',
'w')
as f:
1289 for k, v
in data[lang].items():
1290 f.write(f
'T.{k}={json.dumps(v)}\n')
1293 except Exception
as e:
1294 traceback.print_exc()
1295 results[
'error'] += str(e) +
'\n'
1296 elif command ==
'rename' or command ==
'copy':
1297 app =
StrV(d,
'app')
1298 newname =
StrV(d,
'newname')
1299 oldname =
StrV(d,
'oldname')
1301 if not app
or not newname
or not oldname:
1302 raise Exception(g.T.missingparams)
1304 apppath = os.path.join(
'static', app)
1306 for l
in g.siteconfig[
'languages']:
1307 translationsdir = os.path.join(apppath,
'translations', l)
1309 translationsfile = os.path.join(translationsdir, oldname +
'.js')
1311 if os.path.exists(translationsfile):
1312 if command ==
'rename':
1313 os.replace(translationsfile, os.path.join(translationsdir, newname +
'.js'))
1315 shutil.copy(translationsfile, os.path.join(translationsdir, newname +
'.js'))
1316 elif command ==
'delete':
1317 app =
StrV(d,
'app')
1318 translation =
StrV(d,
'translation')
1320 for l
in g.siteconfig[
'languages']:
1321 translationsfile = os.path.join(
'static', app,
'translations', l, translation +
'.js')
1323 if os.path.exists(translationsfile):
1324 os.remove(translationsfile)
1326 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
1327 except Exception
as e:
1328 traceback.print_exc()
1329 results[
'error'] = str(e)
1335 """Sets security for database objects. Note that the DB_SECURE flag
1336 must be set on both the database and the model
in order to apply.
1341 d = json.loads(g.request.get_data())
1343 command =
StrV(d,
'command')
1345 if command ==
'load':
1346 modelname =
StrV(d,
'model')
1348 objid =
StrV(d,
'objid')
1350 if not model
or not objid:
1351 raise Exception(g.T.missingparams)
1353 g.db.RegisterModel(model)
1355 if not model._dbflags & DB_SECURE:
1356 raise Exception(g.T.cantchange + modelname)
1361 results[
'dbaccess'] = obj.dbaccess
1362 results[
'dbgroup'] =
ToShortCode(obj.dbgroup)
if obj.dbgroup
else ''
1363 results[
'dbgroupaccess'] = obj.dbgroupaccess
1365 acl = obj.GetACL(g.db)
1373 results[
'dbacl'] = acljson
1375 for k, v
in acl[
'users'].items():
1381 if 'admins' in g.session[
'groups']:
1384 g.db.Load(system_user, k,
'displayname').displayname,
1387 except Exception
as e:
1388 userobj[
'displayname'] = f
'Error: {e}'
1390 acljson[
'users'][k] = userobj
1392 for k, v
in acl[
'groups'].items():
1398 if 'admins' in g.session[
'groups']:
1401 g.db.Load(system_group, k,
'displayname').displayname,
1404 except Exception
as e:
1405 groupobj[
'displayname'] = f
'Error: {e}'
1407 acljson[
'groups'][k] = groupobj
1409 results[
'dbacl'] = acljson
1411 if 'admins' in g.session[
'groups']:
1414 g.db.Load(system_user, obj.dbowner,
'displayname').displayname,
1417 except Exception
as e:
1418 results[
'ownername'] = f
'Error: {e}'
1420 results[
'ownername'] = results[
'dbowner']
1424 group = g.db.Load(system_group, obj.dbgroup,
'displayname')
1426 results[
'groupname'] = group.groupname
1431 except Exception
as e:
1432 results[
'groupname'] =
''
1433 results[
'groupdisplayname'] = f
'Error: {e}'
1435 results[
'groupname'] =
''
1436 results[
'groupdisplayname'] =
'[None]'
1437 elif command ==
'searchgroups':
1438 keyword =
StrV(d,
'keyword')
1439 start =
IntV(d,
'start', 0, 99999999, 0)
1440 limit =
IntV(d,
'limit', 10, 99999999, 100)
1446 conds.append(
'(groupname LIKE ? OR displayname LIKE ?)')
1447 params.append(
EscapeSQL(
'%' + keyword +
'%'))
1448 params.append(
EscapeSQL(
'%' + keyword +
'%'))
1451 cond =
'WHERE ' +
' AND '.join(conds)
1456 if 'admins' in g.session[
'groups']:
1463 fields =
'rid, groupname, displayname'
1466 groups = g.db.Linked(
1467 g.db.Load(system_user, g.session.userid,
'rid'),
1472 fields =
'rid, groupname, displayname'
1479 'groupname': r.groupname
1482 results[
'count'] = len(groups)
1483 results[
'data'] = objs
1484 elif command ==
'save':
1485 modelname =
StrV(d,
'model')
1487 objid =
StrV(d,
'objid')
1488 dbowner =
StrV(d,
'dbowner')
1489 dbaccess =
IntV(d,
'dbaccess')
1490 dbgroup =
StrV(d,
'dbgroup')
1491 dbgroupaccess =
StrV(d,
'dbgroupaccess')
1492 dbacl =
DictV(d,
'dbacl')
1494 if not model
or not objid
or not dbowner:
1495 raise Exception(g.T.missingparams)
1497 g.db.RegisterModel(model)
1499 if not model._dbflags & DB_SECURE:
1500 raise Exception(g.T.cantchange + modelname)
1504 acl = obj.GetACL(g.db)
1512 results[
'dbacl'] = acljson
1514 for k, v
in acl[
'users'].items():
1520 if 'admins' in g.session[
'groups']:
1523 g.db.Load(system_user, k,
'displayname').displayname,
1526 except Exception
as e:
1527 userobj[
'displayname'] = f
'Error: {e}'
1529 acljson[
'users'][k] = userobj
1531 for k, v
in acl[
'groups'].items():
1537 if 'admins' in g.session[
'groups']:
1540 g.db.Load(system_group, k,
'displayname').displayname,
1543 except Exception
as e:
1544 groupobj[
'displayname'] = f
'Error: {e}'
1546 acljson[
'groups'][k] = groupobj
1548 results[
'dbacl'] = acljson
1550 if 'admins' in g.session[
'groups']:
1553 g.db.Load(system_user, obj.dbowner,
'displayname').displayname,
1556 except Exception
as e:
1557 results[
'ownername'] = f
'Error: {e}'
1559 results[
'ownername'] = results[
'dbowner']
1563 group = g.db.Load(system_group, obj.dbgroup,
'displayname')
1565 results[
'groupname'] = group.groupname
1570 except Exception
as e:
1571 results[
'groupname'] =
''
1572 results[
'groupdisplayname'] = f
'Error: {e}'
1574 results[
'groupname'] =
''
1575 results[
'groupdisplayname'] =
'[None]'
1577 results[
'error'] = f
'{g.T.cantunderstand} "{command}"'
1578 except Exception
as e:
1579 traceback.print_exc()
1580 results[
'error'] = str(e)
1588 'controlpanelapi': controlpanelapi,
1591 'sessionvars': sessionvars,
1592 'translationsapi': translationsapi,
1593 'securityapi': securityapi,
The main database object.
def InitDB(g)
Setups the initial database users, groups, and pages.
def StrV(d, k, default='')
Utility function to get a string from a dict or object given its name.
def DictV(d, k, default={})
Utility function to get a dict from a dict or object given its name.
def ArrayV(d, k, default=[])
Utility function to get an array from a dict or object given its name.
def IntV(d, k, min=None, max=None, default=0)
Utility function to get an int from a dict or object given its name.
def V(d, k, default=None)
Utility function to get a value from a dict or object given its name.
def EscapeSQL(s)
Escapes special characters to be used in a SQL query string.
def FromShortCode(code, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a six character alphanumeric code into a 32-bit value.
def GetModel(model)
Returns the model object from the correct module.
def ToShortCode(val, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a 32-bit value into a six character alphanumeric code.
def BestTranslation(translations, languages, defaultlang='en')
Returns the best translation found in a dict of different translations.
def b64decodepad(s)
Helper function to ensure that a b64 string is correctly padded to four spaces.
def CodeDir(code)
Separates a filename into three character segments with the directory separator '/' in between.
def LoadTranslation(g, app, translation, languages)
Load the translation into the global language dict in g.T.
def translationsapi(g)
Handles frequently used translations for the system.
def controlpanelapi(g)
Configures settings for the whole site.
def dbapi(g)
Used by the web database management interface to handle objects in the system.
def fileapi(g)
Standard API to search through the files on the system and change names or descriptions.
def upload(g)
Handles uploads for the whole system.
def securityapi(g)
Sets security for database objects.
def sessionvars(g)
In order to enable caching for static pages, we place all session and user specific information in th...
def userapi(g)
Handles logins, registrations, approvals, and user information.