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: | 402 / 530 |
| Referenced in: | [show references] |