Uses 1059K of libraries. Compilation Failed (27943L/168K).
1 | !7 |
2 | |
3 | // trying to move to /admin, but that's complicated |
4 | |
5 | sS passwordSalt() { |
6 | ret loadTextFileOrCreateWithRandomID(javaxSecretDir("password-salt")); |
7 | } |
8 | |
9 | concept PostReferenceable {} |
10 | |
11 | concept User > PostReferenceable { |
12 | S name; |
13 | SecretValue<S> passwordMD5; |
14 | S contact; // e.g. mail address |
15 | bool isMaster; |
16 | SecretValue<S> botToken = aSecretGlobalID/*UnlessLoading*/(); |
17 | |
18 | toString { ret nempty(name) ? "User " + name : super.toString(); } |
19 | } |
20 | |
21 | extend AuthedDialogID { |
22 | new Ref<User> user; // who is logged in |
23 | bool userMode; // show things as if user was not master |
24 | } |
25 | |
26 | concept UserCreatedObject > PostReferenceable { |
27 | new Ref<User> creator; |
28 | S botInfo; // info on how this was made |
29 | //S nameInRepo; // unique by user, for URL |
30 | } |
31 | |
32 | concept UserPost > UserCreatedObject { |
33 | S type, title, text; |
34 | bool isPublic = true; |
35 | |
36 | S text() { ret text; } |
37 | |
38 | new RefL<PostReferenceable> postRefs; |
39 | new LS postRefTags; |
40 | |
41 | sS _fieldOrder = "isPublic text type title postRefs"; |
42 | |
43 | toString { |
44 | ret "Post by " + userName(creator!) + ": " + (nempty(title) ? shorten(title) : shorten(text)); |
45 | } |
46 | } |
47 | |
48 | /*concept BotHandle > UserCreatedObject { |
49 | S globalID = aGlobalIDUnlessLoading(); |
50 | S comment; |
51 | SecretValue<S> token; |
52 | }*/ |
53 | |
54 | !include once #1029928 // new Compact Module include |
55 | set flag NoNanoHTTPD. |
56 | |
57 | /*c*/module GazelleExamples > DynNewBot2 { |
58 | void start { |
59 | baseLink = "/admin"; |
60 | botName = heading = adminName = "gazelle.rocks"; |
61 | set enableUsers; |
62 | set useWebSockets; |
63 | set showRegisterLink; |
64 | unset showTalkToBotLink; |
65 | set alwaysRedirectToHttps; |
66 | passwordSalt(); // make early |
67 | super.start(); |
68 | db_mainConcepts().modifyOnCreate = true; |
69 | indexConceptFieldCI(User, "name"); |
70 | } |
71 | |
72 | L<Class> crudClasses(Req req) { |
73 | L<Class> l = super.crudClasses(req); |
74 | l.add(UserPost); |
75 | if (req.masterAuthed) |
76 | l.add(User); |
77 | ret l; |
78 | } |
79 | |
80 | S serveRegisterForm(SS params) { |
81 | S user = trim(params.get("user")); |
82 | S pw = trim(params.get("f_pw")); |
83 | S pw2 = trim(params.get("pw2")); |
84 | S redirect = params.get("redirect"); |
85 | S contact = trim(params.get("contact")); |
86 | |
87 | redirect = dropParamFromURL(redirect, "logout"); // don't log us out right again |
88 | if (empty(redirect)) redirect = baseLink + "/"; |
89 | |
90 | new LS msgs; |
91 | if (nempty(user) || nempty(pw)) { |
92 | lock dbLock(); |
93 | if (empty(user)) msgs.add("Please enter a user name"); |
94 | else if (l(user) < 4) msgs.add("Minimum user name length: 4 characters"); |
95 | else if (l(user) > 30) msgs.add("Maximum user name length: 30 characters"); |
96 | else if (hasConceptIC User(name := user)) |
97 | msgs.add("This user exists, please choose a different name"); |
98 | if (regexpContainsIC("[^a-z0-9\\-\\.]", user)) |
99 | msgs.add("Bad characters in user name (please use only a-z, 0-9, - and .)"); |
100 | if (empty(pw)) msgs.add("Please enter a password"); |
101 | else if (l(pw) < 6) msgs.add("Minimum password length: 6 characters"); |
102 | if (neq(pw, pw2)) msgs.add("Passwords don't match"); |
103 | |
104 | if (empty(msgs)) { |
105 | cnew User(name := user, passwordMD5 := SecretValue(hashPW(pw)), +contact); |
106 | ret hrefresh(5.0, redirect) + "User " + user + " created! Redirecting..."; |
107 | } |
108 | } |
109 | |
110 | ret hhtml(hhead(htitle("Register new user") |
111 | + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( |
112 | h3_htmlEncode(adminName + " | Register new user") |
113 | + hpostform( |
114 | hhidden(+redirect) |
115 | + tag table( |
116 | (empty(msgs) ? "" : tr(td() + td() + td(htmlEncode2_nlToBr(lines_rtrim(msgs))))) |
117 | + tr(td_valignTop("Choose a user name:") |
118 | + td_valignTop(hinputfield(+user) + "<br>" + "(Minimum length 4. Characters allowed: a-z, 0-9, - and .)")) |
119 | + tr(td_valignTop("Choose a password:") |
120 | + td_valignTop(hpassword(f_pw := pw) + "<br>" + "(Minimum length 6 characters)")) |
121 | + tr(td_valignTop("Repeat the password please:") + td(hpassword(+pw2))) |
122 | + tr(td_valignTop("Way to contact you (e.g. e-mail) - optional:") + td(hpassword(+contact))) |
123 | + tr(td() + td(hsubmit("Register"))), class := "responstable"), |
124 | action := baseLink + "/register") |
125 | ))); |
126 | } |
127 | |
128 | bool calcMasterAuthed(Req req) { |
129 | ret super.calcMasterAuthed(req) |
130 | || req.auth != null && req.auth.user.has() && req.auth.user->isMaster; |
131 | } |
132 | |
133 | O serveOtherPage(Req req) { |
134 | S uri = req.uri; |
135 | new Matches m; |
136 | |
137 | if (eq(uri, "/register")) |
138 | ret serveRegisterForm(req.params); |
139 | |
140 | if (eq(uri, "/becomeMaster") && req.auth != null && req.auth.user != null) { |
141 | S pw = trim(req.params.get("masterPW")); |
142 | if (eq(pw, realPW())) { |
143 | cset(req.auth.user, isMaster := true); |
144 | ret "You are master, " + req.auth.user + "!"; |
145 | } |
146 | if (nempty(pw)) |
147 | ret "Bad master PW"; |
148 | ret hhtml(hhead(htitle("Become master") |
149 | + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( |
150 | h3_htmlEncode(adminName + " | Become master") |
151 | + hpostform( |
152 | tag table( |
153 | tr(td_valignTop("You are:") |
154 | + td_valignTop(htmlEncode2(req.auth.user->name))) |
155 | + tr(td_valignTop("Enter the master password:") |
156 | + td_valignTop(hpassword(masterPW := pw))) |
157 | + tr(td() + td(hsubmit("Become master user"))), class := "responstable"), |
158 | action := baseLink + "/becomeMaster") |
159 | ))); |
160 | } |
161 | |
162 | if (swic(uri, "/text/", m)) { |
163 | long id = parseLong(m.rest()); |
164 | UserPost post = getConceptOpt UserPost(id); |
165 | if (post == null) ret serve404("Post with ID " + id + " not found"); |
166 | if (!post.isPublic) ret serve404("Post is not public"); |
167 | ret serveText(post.text()); |
168 | } |
169 | |
170 | S uri2 = dropSlashPrefix(uri); |
171 | if (isInteger(uri2)) { |
172 | long id = parseLong(uri2); |
173 | UserPost post = getConceptOpt UserPost(id); |
174 | if (post == null) ret serve404("Post with ID " + id + " not found"); |
175 | ret servePost(post); |
176 | } |
177 | |
178 | try object serveOtherPage2(req); |
179 | |
180 | ret super.serveOtherPage(req); |
181 | } |
182 | |
183 | !include #1029962 // serveOtherPage2 |
184 | |
185 | O servePost(UserPost post) { |
186 | if (!post.isPublic) ret serve404("Post is not public"); |
187 | new HTMLFramer1 framer; |
188 | framer.title = or2(post.title, shorten(post.text)); |
189 | framer.add(p("By " + (post.creator.has() ? htmlEncode2(post.creator->name) : "?") + ". " + renderConceptDate(post))); |
190 | if (nempty(post.type)) |
191 | framer.add(p("Post type: "+ htmlEncode2(post.type))); |
192 | framer.add(hsourcecode(post.text)); |
193 | |
194 | // show render link |
195 | if (eqic(post.type, "HTML")) |
196 | framer.add(p(ahref("/html/" + post.id, "Show as HTML page"))); |
197 | |
198 | // show edit link |
199 | if (canEditPost(post)) |
200 | framer.add(p(ahref(conceptEditLink(post), "Edit post"))); |
201 | |
202 | // show reply link |
203 | framer.add(p(ahref(replyLink(post), "Reply to this post"))); |
204 | |
205 | // show post refs |
206 | L<PostReferenceable> postRefs = cloneList(post.postRefs); |
207 | if (nempty(postRefs)) { |
208 | framer.add(p("In reference to:")); |
209 | framer.add(ul(cloneMap(postRefs, ref -> |
210 | ahref(objectLink(ref), htmlEncode_nlToBr_withIndents(str(ref)))))); |
211 | } |
212 | |
213 | // show referencing posts |
214 | Cl<UserPost> refs = sortedByConceptID(instancesOf UserPost(allBackRefs(post))); |
215 | if (nempty(refs)) { |
216 | framer.add(p("Referenced by posts:")); |
217 | framer.add(ul(lmap objectToHTML(refs))); |
218 | } |
219 | ret framer.render(); |
220 | } |
221 | |
222 | S serveAuthForm(S redirect) { |
223 | ret hAddToHead(super.serveAuthForm(redirect), hInitWebSocket()); |
224 | } |
225 | |
226 | // handle user log-in (create/update AuthedDialogID concept) |
227 | O handleAuth(Req req, S cookie) null { |
228 | S name = trim(req.params.get('user)), pw = trim(req.params.get('pw)); |
229 | if (nempty(name) && nempty(pw) && nempty(cookie)) { |
230 | Domain authDomain; |
231 | User user = conceptWhereCI User(+name); |
232 | if (user == null) |
233 | ret errorMsg("User " + htmlEncode2(name) + " not found"); |
234 | if (!eq(getVar(user.passwordMD5), hashPW(pw))) |
235 | ret errorMsg("Bad password, please try again"); |
236 | |
237 | // auth |
238 | cset(uniq AuthedDialogID(+cookie), restrictedToDomain := null, master := user.isMaster, +user); |
239 | |
240 | S redirect = req.params.get('redirect); |
241 | if (nempty(redirect)) |
242 | ret hrefresh(redirect); |
243 | } |
244 | } |
245 | |
246 | O serve2(Req req) { |
247 | S userMode = req.params.get("userMode"); |
248 | if (nempty(userMode) && req.auth != null) |
249 | req.auth.userMode = eq(userMode, "1"); |
250 | |
251 | ret super.serve2(req); |
252 | } |
253 | |
254 | S hashPW(S pw) { ret md5(pw + passwordSalt()); } |
255 | |
256 | bool inMasterMode(Req req) { |
257 | ret req.masterAuthed && !req.auth.userMode; |
258 | } |
259 | |
260 | MapSO filtersForClass(Class c, Req req) { |
261 | if (c == UserPost) { |
262 | if (!inMasterMode(req)) |
263 | ret litmap(creator := req.auth.user!); |
264 | } |
265 | ret super.filtersForClass(c, req); |
266 | } |
267 | |
268 | S loggedInUserDesc(Req req) { |
269 | ret req.auth == null ? "not logged in" : req.auth.user! == null ? (req.masterAuthed ? "master user" : "weird user") : "user " + req.auth.user->name; |
270 | } |
271 | |
272 | <A extends Concept> HCRUD_Concepts<A> crudData(Class<A> c, Req req) { |
273 | HCRUD_Concepts<A> cc = super.crudData(c, req); |
274 | if (c == UserPost) { |
275 | cc.addRenderer("text", new HCRUD_Data.TextArea(80, 10)); |
276 | cc.fieldHelp( |
277 | type := "Type of post (any format, optional)", |
278 | title := "Title for this post (any format, optional)", |
279 | text := "Text contents of this post (any format)", |
280 | nameInRepo := "Name in repository (not really used yet)", |
281 | botInfo := "Info on which bot made this post (if any)"); |
282 | cc.massageItemMapForList = (item, map) -> { |
283 | applyFunctionToValue shorten(map, "text", "title", "type", "nameInRepo"); |
284 | }; |
285 | cc.emptyObject = () -> { |
286 | MapSO item = cc.emptyObject_base(); |
287 | item.put(creator := req.auth.user!); // set default creator to current user if in master mode |
288 | ret item; |
289 | }; |
290 | } |
291 | ret cc; |
292 | } |
293 | |
294 | <A extends Concept> HCRUD makeCRUD(Class<A> c, Req req) { |
295 | HCRUD crud = super.makeCRUD(c, req); |
296 | if (c == UserPost) { |
297 | crud.renderCmds = map -> { |
298 | UserPost post = getConcept UserPost(toLong(crud.itemID(map))); |
299 | ret joinNemptiesWithVBar(crud.renderCmds_base(map), |
300 | targetBlank(postLink(post), "Show post")); |
301 | }; |
302 | crud.uneditableFields = litset("postRefTags"); |
303 | } |
304 | |
305 | if (c == User) { |
306 | crud.allowCreate = false; |
307 | crud.uneditableFields = litset("passwordMD5"); |
308 | } |
309 | |
310 | ret crud; |
311 | } |
312 | |
313 | S authFormHeading() { |
314 | ret h3_htmlEncode("Welcome to the Gazelle AI System") |
315 | + p(hsnippetimg_scaleToWidth(200, #1101482, 425, 257, title := "Gazelle")); |
316 | } |
317 | |
318 | void makeFramer(Req req) { |
319 | super.makeFramer(req); |
320 | if (req.masterAuthed) |
321 | req.framer.add(p_vbar( |
322 | ahrefIf(req.auth.userMode, appendQueryToURL(baseLink + req.uri, userMode := 0), "Master mode", title := "View pages as master"), |
323 | ahrefIf(!req.auth.userMode, appendQueryToURL(baseLink + req.uri, userMode := 1), "User mode", title := "View pages as user"))); |
324 | } |
325 | |
326 | // link to show post |
327 | S postLink(UserPost post) { |
328 | ret objectLink(post); |
329 | } |
330 | |
331 | S objectLink(Concept c) { |
332 | ret baseLink + "/" + c.id; |
333 | } |
334 | |
335 | S objectToHTML(Concept c) { |
336 | ret ahref(objectLink(c), htmlEncode_nlToBr_withIndents(str(c))); |
337 | } |
338 | |
339 | bool canEditPost(UserPost post) { |
340 | Req req = currentReq!; |
341 | ret req != null && req.auth != null && req.auth.user! != null |
342 | && (req.auth.user->isMaster || req.auth.user! == post.creator!); |
343 | } |
344 | |
345 | S replyLink(UserPost post) { |
346 | ret appendQueryToURL(crudLink(UserPost), cmd := "new", fList_postRefs_0 := post.id); |
347 | } |
348 | } // end of module |
349 | |
350 | sS userName(User user) { |
351 | ret user != null ? user.name : "?"; |
352 | } |
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt
No comments. add comment
Snippet ID: | #1029968 |
Snippet name: | gazelle.rocks v2 [abandoned] |
Eternal ID of this version: | #1029968/5 |
Text MD5: | 856eca7b06f25f3c29d0203152beeca9 |
Transpilation MD5: | 047f23829d2300987249196c34d75644 |
Author: | stefan |
Category: | |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-11-08 13:33:31 |
Source code size: | 11958 bytes / 352 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 166 / 251 |
Version history: | 4 change(s) |
Referenced in: | [show references] |