Uses 1059K of libraries. Click here for Pure Java version (26964L/160K).
1 | !7 |
2 | |
3 | sS passwordSalt() { |
4 | ret loadTextFileOrCreateWithRandomID(javaxSecretDir("password-salt")); |
5 | } |
6 | |
7 | concept User { |
8 | S name; |
9 | SecretValue<S> passwordMD5; |
10 | S contact; // e.g. mail address |
11 | bool isMaster; |
12 | |
13 | toString { ret or2(name, super.toString()); } |
14 | } |
15 | |
16 | extend AuthedDialogID { |
17 | new Ref<User> user; // who is logged in |
18 | bool userMode; // show things as if user was not master |
19 | } |
20 | |
21 | concept UserCreatedObject { |
22 | new Ref<User> creator; |
23 | //S nameInRepo; // unique by user, for URL |
24 | bool isPublic = true; |
25 | } |
26 | |
27 | concept UserPost > UserCreatedObject { |
28 | S type, title, text; |
29 | |
30 | S text() { ret text; } |
31 | |
32 | sS _fieldOrder = "isPublic text type title"; |
33 | } |
34 | |
35 | concept PostReference { |
36 | new Ref<UserPost> post; |
37 | new Ref<Concept> referenced; |
38 | S as; |
39 | } |
40 | |
41 | !include once #1029928 // new Compact Module include |
42 | set flag NoNanoHTTPD. |
43 | |
44 | /*c*/module GazelleExamples > DynNewBot2 { |
45 | void start { |
46 | botName = heading = adminName = "gazelle.rocks"; |
47 | set enableUsers; |
48 | set useWebSockets; |
49 | set showRegisterLink; |
50 | unset showTalkToBotLink; |
51 | set alwaysRedirectToHttps; |
52 | passwordSalt(); // make early |
53 | super.start(); |
54 | db_mainConcepts().modifyOnCreate = true; |
55 | indexConceptFieldCI(User, "name"); |
56 | } |
57 | |
58 | L<Class> crudClasses(Req req) { |
59 | L<Class> l = super.crudClasses(req); |
60 | l.add(UserPost); |
61 | if (req.masterAuthed) |
62 | l.add(User); |
63 | ret l; |
64 | } |
65 | |
66 | S serveRegisterForm(SS params) { |
67 | S user = trim(params.get("user")); |
68 | S pw = trim(params.get("f_pw")); |
69 | S pw2 = trim(params.get("pw2")); |
70 | S redirect = params.get("redirect"); |
71 | S contact = trim(params.get("contact")); |
72 | |
73 | redirect = dropParamFromURL(redirect, "logout"); // don't log us out right again |
74 | if (empty(redirect)) redirect = baseLink + "/"; |
75 | |
76 | new LS msgs; |
77 | if (nempty(user) || nempty(pw)) { |
78 | lock dbLock(); |
79 | if (empty(user)) msgs.add("Please enter a user name"); |
80 | else if (l(user) < 4) msgs.add("Minimum user name length: 4 characters"); |
81 | else if (l(user) > 30) msgs.add("Maximum user name length: 30 characters"); |
82 | else if (hasConceptIC User(name := user)) |
83 | msgs.add("This user exists, please choose a different name"); |
84 | if (regexpContainsIC("[^a-z0-9\\-\\.]", user)) |
85 | msgs.add("Bad characters in user name (please use only a-z, 0-9, - and .)"); |
86 | if (empty(pw)) msgs.add("Please enter a password"); |
87 | else if (l(pw) < 6) msgs.add("Minimum password length: 6 characters"); |
88 | if (neq(pw, pw2)) msgs.add("Passwords don't match"); |
89 | |
90 | if (empty(msgs)) { |
91 | cnew User(name := user, passwordMD5 := SecretValue(hashPW(pw)), +contact); |
92 | ret hrefresh(5.0, redirect) + "User " + user + " created! Redirecting..."; |
93 | } |
94 | } |
95 | |
96 | ret hhtml(hhead(htitle("Register new user") |
97 | + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( |
98 | h3_htmlEncode(adminName + " | Register new user") |
99 | + hpostform( |
100 | hhidden(+redirect) |
101 | + tag table( |
102 | (empty(msgs) ? "" : tr(td() + td() + td(htmlEncode2_nlToBr(lines_rtrim(msgs))))) |
103 | + tr(td_valignTop("Choose a user name:") |
104 | + td_valignTop(hinputfield(+user) + "<br>" + "(Minimum length 4. Characters allowed: a-z, 0-9, - and .)")) |
105 | + tr(td_valignTop("Choose a password:") |
106 | + td_valignTop(hpassword(f_pw := pw) + "<br>" + "(Minimum length 6 characters)")) |
107 | + tr(td_valignTop("Repeat the password please:") + td(hpassword(+pw2))) |
108 | + tr(td_valignTop("Way to contact you (e.g. e-mail) - optional:") + td(hpassword(+contact))) |
109 | + tr(td() + td(hsubmit("Register"))), class := "responstable"), |
110 | action := baseLink + "/register") |
111 | ))); |
112 | } |
113 | |
114 | bool calcMasterAuthed(Req req) { |
115 | ret super.calcMasterAuthed(req) |
116 | || req.auth != null && req.auth.user.has() && req.auth.user->isMaster; |
117 | } |
118 | |
119 | O serveOtherPage(Req req) { |
120 | S uri = req.uri; |
121 | new Matches m; |
122 | |
123 | if (eq(uri, "/register")) |
124 | ret serveRegisterForm(req.params); |
125 | |
126 | if (eq(uri, "/becomeMaster") && req.auth != null && req.auth.user != null) { |
127 | S pw = trim(req.params.get("masterPW")); |
128 | if (eq(pw, realPW())) { |
129 | cset(req.auth.user, isMaster := true); |
130 | ret "You are master, " + req.auth.user + "!"; |
131 | } |
132 | if (nempty(pw)) |
133 | ret "Bad master PW"; |
134 | ret hhtml(hhead(htitle("Become master") |
135 | + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( |
136 | h3_htmlEncode(adminName + " | Become master") |
137 | + hpostform( |
138 | tag table( |
139 | tr(td_valignTop("You are:") |
140 | + td_valignTop(htmlEncode2(req.auth.user->name))) |
141 | + tr(td_valignTop("Enter the master password:") |
142 | + td_valignTop(hpassword(masterPW := pw))) |
143 | + tr(td() + td(hsubmit("Become master user"))), class := "responstable"), |
144 | action := baseLink + "/becomeMaster") |
145 | ))); |
146 | } |
147 | |
148 | if (swic(uri, "/text/", m)) { |
149 | long id = parseLong(m.rest()); |
150 | UserPost post = getConceptOpt UserPost(id); |
151 | if (post == null) ret serve404("Post with ID " + id + " not found"); |
152 | if (!post.isPublic) ret serve404("Post is not public"); |
153 | ret serveText(post.text()); |
154 | } |
155 | |
156 | S uri2 = dropSlashPrefix(uri); |
157 | if (isInteger(uri2)) { |
158 | long id = parseLong(uri2); |
159 | UserPost post = getConceptOpt UserPost(id); |
160 | if (post == null) ret serve404("Post with ID " + id + " not found"); |
161 | ret servePost(post); |
162 | } |
163 | |
164 | try object serveOtherPage2(req); |
165 | |
166 | ret super.serveOtherPage(req); |
167 | } |
168 | |
169 | !include #1029962 // serveOtherPage2 |
170 | |
171 | O servePost(UserPost post) { |
172 | if (!post.isPublic) ret serve404("Post is not public"); |
173 | new HTMLFramer1 framer; |
174 | framer.title = post.title; |
175 | framer.add(p("By " + (post.creator.has() ? htmlEncode2(post.creator->name) : "?") + ". " + renderConceptDate(post))); |
176 | if (nempty(post.type)) |
177 | framer.add(p("Post type: "+ htmlEncode2(post.type))); |
178 | framer.add(hsourcecode(post.text)); |
179 | ret framer.render(); |
180 | } |
181 | |
182 | S serveAuthForm(S redirect) { |
183 | ret hAddToHead(super.serveAuthForm(redirect), hInitWebSocket()); |
184 | } |
185 | |
186 | // handle user log-in (create/update AuthedDialogID concept) |
187 | O handleAuth(Req req, S cookie) null { |
188 | S name = trim(req.params.get('user)), pw = trim(req.params.get('pw)); |
189 | if (nempty(name) && nempty(pw) && nempty(cookie)) { |
190 | Domain authDomain; |
191 | User user = conceptWhereCI User(+name); |
192 | if (user == null) |
193 | ret errorMsg("User " + htmlEncode2(name) + " not found"); |
194 | if (!eq(getVar(user.passwordMD5), hashPW(pw))) |
195 | ret errorMsg("Bad password, please try again"); |
196 | |
197 | // auth |
198 | cset(uniq AuthedDialogID(+cookie), restrictedToDomain := null, master := user.isMaster, +user); |
199 | |
200 | S redirect = req.params.get('redirect); |
201 | if (nempty(redirect)) |
202 | ret hrefresh(redirect); |
203 | } |
204 | } |
205 | |
206 | O serve2(Req req) { |
207 | S userMode = req.params.get("userMode"); |
208 | if (nempty(userMode) && req.auth != null) |
209 | req.auth.userMode = eq(userMode, "1"); |
210 | |
211 | ret super.serve2(req); |
212 | } |
213 | |
214 | S hashPW(S pw) { ret md5(pw + passwordSalt()); } |
215 | |
216 | bool inMasterMode(Req req) { |
217 | ret req.masterAuthed && !req.auth.userMode; |
218 | } |
219 | |
220 | MapSO filtersForClass(Class c, Req req) { |
221 | if (c == UserPost) { |
222 | if (!inMasterMode(req)) |
223 | ret litmap(creator := req.auth.user!); |
224 | } |
225 | ret super.filtersForClass(c, req); |
226 | } |
227 | |
228 | S loggedInUserDesc(Req req) { |
229 | ret req.auth == null ? "not logged in" : req.auth.user! == null ? (req.masterAuthed ? "master user" : "weird user") : "user " + req.auth.user->name; |
230 | } |
231 | |
232 | <A extends Concept> HCRUD_Concepts<A> crudData(Class<A> c) { |
233 | HCRUD_Concepts<A> cc = super.crudData(c); |
234 | if (c == UserPost) { |
235 | cc.addRenderer("text", new HCRUD_Data.TextArea(80, 10)); |
236 | cc.fieldHelp( |
237 | type := "Type of post (any format, optional)", |
238 | title := "Title for this post (any format, optional)", |
239 | text := "Text contents of this post (any format)", |
240 | nameInRepo := "Name in repository (not really used yet)"); |
241 | cc.massageItemMapForList = (item, map) -> { |
242 | applyFunctionToValue shorten(map, "text", "title", "type", "nameInRepo"); |
243 | }; |
244 | } |
245 | ret cc; |
246 | } |
247 | |
248 | <A extends Concept> HCRUD makeCRUD(Class<A> c, Req req) { |
249 | HCRUD crud = super.makeCRUD(c, req); |
250 | if (c == UserPost) |
251 | crud.renderCmds = map -> { |
252 | UserPost post = getConcept UserPost(toLong(crud.itemID(map))); |
253 | ret joinNemptiesWithVBar(crud.renderCmds_base(map), |
254 | targetBlank(postLink(post), "Show post")); |
255 | }; |
256 | if (c == User) |
257 | crud.uneditableFields = litset("passwordMD5"); |
258 | |
259 | ret crud; |
260 | } |
261 | |
262 | S authFormHeading() { |
263 | ret h3_htmlEncode("Welcome to the Gazelle AI System") |
264 | + p(hsnippetimg_scaleToWidth(200, #1101482, 425, 257, title := "Gazelle")); |
265 | } |
266 | |
267 | void makeFramer(Req req) { |
268 | super.makeFramer(req); |
269 | if (req.masterAuthed) |
270 | req.framer.add(p_vbar( |
271 | ahrefIf(req.auth.userMode, appendQueryToURL(baseLink + req.uri, userMode := 0), "Master mode", title := "View pages as master"), |
272 | ahrefIf(!req.auth.userMode, appendQueryToURL(baseLink + req.uri, userMode := 1), "User mode", title := "View pages as user"))); |
273 | } |
274 | |
275 | // link to show post |
276 | S postLink(UserPost post) { |
277 | ret baseLink + "/" + post.id; |
278 | } |
279 | } // end of module |
Began life as a copy of #1029913
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt
No comments. add comment
Snippet ID: | #1029963 |
Snippet name: | gazelle.rocks [working backup before post refs] |
Eternal ID of this version: | #1029963/1 |
Text MD5: | 14fccc989acca0a1077c9cae4929c4d8 |
Transpilation MD5: | 8949d69ba8d218ef7a1fcf52c6cd3755 |
Author: | stefan |
Category: | javax |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-10-23 10:23:09 |
Source code size: | 9564 bytes / 279 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 195 / 287 |
Referenced in: | [show references] |