1 | !7 |
2 | |
3 | // See #1023660 for the older 99 lines version |
4 | |
5 | // in main DB |
6 | |
7 | concept PhysicalSlice { |
8 | S globalID = aGlobalID(); // also the directory name |
9 | S name; |
10 | } |
11 | |
12 | // in slice DBs |
13 | |
14 | concept Page { |
15 | S globalID = aGlobalID(); |
16 | S url; |
17 | } |
18 | |
19 | concept Entry { |
20 | S globalID = aGlobalID(); |
21 | new Ref<Page> page; |
22 | int count; |
23 | S key, value; |
24 | S ip; |
25 | new Ref<Signer> signer; |
26 | S signerID; // global ID of signer |
27 | } |
28 | |
29 | concept Signer { |
30 | S globalID = aGlobalID(); |
31 | S publicKey; |
32 | bool trusted; |
33 | S approvedBy; |
34 | } |
35 | |
36 | static int displayLength = 140; |
37 | |
38 | static int lines; |
39 | sbool allowMultipleCasesInValues = true; |
40 | static ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; |
41 | |
42 | sclass SliceDB { |
43 | PhysicalSlice slice; |
44 | Concepts concepts; |
45 | } |
46 | |
47 | p { |
48 | dbIndexingCI(Page, 'url, Entry, 'key, Entry, 'value); |
49 | dbIndexing(Signer, 'publicKey); |
50 | idx_latestCreatedPages = new ConceptFieldIndexDesc(Page, 'created); |
51 | idx_latestChangedPages = new ConceptFieldIndexDesc(Page, '_modified); |
52 | idx_latestEntries = new ConceptFieldIndexDesc(Entry, 'created); |
53 | |
54 | // Approve this machine's key |
55 | PKIKeyPair machineKey = agiBot_trustedKeyForMachine(); |
56 | if (machineKey != null) { |
57 | print("Approving this machine's key: " + machineKey.publicKey); |
58 | cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local"); |
59 | } |
60 | |
61 | lines = countLines(mySource()); |
62 | } |
63 | |
64 | // DB functions |
65 | |
66 | sbool hasPage(S url) { ret hasConceptWhereIC(Page, +url); } |
67 | sS getValue(Page page, S key) { |
68 | ret getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key))); |
69 | } |
70 | sS pageDisplayName(Page page) { |
71 | S name = getValue(page, "read as"); |
72 | bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url); |
73 | ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url)); |
74 | } |
75 | |
76 | // Serve page |
77 | |
78 | set flag NoNanoHTTPD. html { |
79 | new Matches m; |
80 | |
81 | if (swic(uri, "/bot/", m)) ret serveBot("/" + m.rest(), params); |
82 | |
83 | S q = params.get('q); |
84 | S domain = or2(params.get('domain), domain()); |
85 | if (nempty(q)) { |
86 | domain = makeAGIDomain(q); |
87 | if (l(domain) > maximumDomainPartLength()) // escape with "domain=" |
88 | ret hrefresh("http://agi.blue/" + hquery(+domain, key := "read as", value := q)); |
89 | ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q))); |
90 | //uri = "/"; replaceMapWithParams(params, key := "read as", value := q); |
91 | } |
92 | S url = domain + dropTrailingSlash(uri); |
93 | Page page, bool newPage = unpair uniqCI2_sync(Page, +url); |
94 | if (newPage) dbLog("New page", +url); |
95 | |
96 | S top = hcomment("new") + p(ahref("http://agi.blue", hsnippetimg(#1101682, width := 565/5, height := 800/5, title := "Robot by Peerro @ DeviantArt"))) |
97 | + p(small(b(ahref("http://agi.blue", "agi.blue")) |
98 | + " | " + targetBlank(progLink(), "source code") + " of this web site (" + nLines(lines) + ") | " + targetBlank("https://gitter.im/agi-blue/community", "sponsor https?") + " | by " + targetBlank("https://BotCompany.de", "BC") + " | " + targetBlank("http://fiverr.tinybrain.de/", "Fiverr") + " | " + targetBlank("https://discordapp.com/invite/SEAjPqk", "Discord") + " | " + targetBlank("https://www.youtube.com/watch?v=b6jtRdV3Ev8", "Video"))); |
99 | |
100 | if (eqicOneOf(url, "agi.blue", "www.agi.blue")) |
101 | ret hhtml(hhead_title("agi.blue Overview") // HOME PAGE |
102 | + hbody_fullcenter(top |
103 | + hform(b("GIVE ME INPUT:") + "<br><br>" + htextinput('q) + " " + hsubmit("Ask")) |
104 | + h1("agi.blue has " + nPages(countConcepts(Page))) |
105 | + p(nlToBr(nemptyLines(map(func(Page p) -> S { |
106 | ahref(fixAGILink("http://" + p.url), htmlEncode2(pageDisplayName(p))) |
107 | }, sortedByFieldDesc _modified(list(Page)))))) |
108 | )); |
109 | |
110 | S key = trim(params.get('key)), value = trim(params.get('value)); |
111 | L<Entry> entries = GetEntriesAndPost().go(page, params).entries; |
112 | |
113 | S get = params.get('get); |
114 | if (nempty(get)) |
115 | ret serveText(jsonEncode(collect value(llNotNulls(firstWhereIC(entries, key := get))))); |
116 | |
117 | S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset |
118 | |
119 | bool withHidden = eq(params.get('withHidden), "1"); |
120 | new Set<Int> hide; |
121 | if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value))); |
122 | new MultiMap<Int, S> mmMeta; |
123 | for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value); |
124 | new MultiMap<S> mm; |
125 | for (Entry e : entries) mm.put(e.key, e.value); |
126 | |
127 | S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url); |
128 | |
129 | ret hhtml(hhead_title(pageDisplayName(page)) + hbody(hfullcenterAndTopRight( |
130 | top + h1(ahref_unstyled("http://" + url, htmlEncode2(name)) + (newPage ? " [huh????]" : "")) |
131 | + p(nlToBr(nemptyLines(map(entries, func(Entry e) -> S { |
132 | !withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? "" : "[" + e.count + "] " + |
133 | htmlencode2(e.key) + ": " + b( |
134 | isURL(e.value) |
135 | || cic(mmMeta.get(e.count), "is a URL") |
136 | || isAGIDomain(e.value) |
137 | ? ahref(fixAGILink(absoluteURL(e.value)), htmlencode2(shorten(displayLength, e.value))) |
138 | : ahref(agiBlue_linkForPhrase(e.value), htmlencode2(shorten(displayLength, e.value))) |
139 | ) })))) |
140 | + hpostform(h3("Add an entry") |
141 | + "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "<br><br>" + hsubmit("Add") |
142 | ) |
143 | , hform(b("GIVE ME INPUT:") + " " + htextinput('q) + " " + hsubmit("Ask"))))); |
144 | } |
145 | |
146 | svoid dbLog(O... params) { |
147 | logStructure(programFile("db.log"), ll(params)); |
148 | } |
149 | |
150 | // uri = without the "/bot" in front |
151 | sO serveBot(S uri, SS params) { |
152 | S q = params.get('q), url = params.get('url); |
153 | if (nempty(q)) url = makeAGIDomain(q); |
154 | if (eqic(uri, "/hello")) ret serveJSON("hello"); |
155 | if (eqic(uri, "/hasPage")) ret serveJSON(hasPage(url)); |
156 | if (eqic(uri, "/randomPageContaining")) { |
157 | assertNempty(q); |
158 | ret servePageToBot(random(filter(list(Page), p -> cic(p.url, q))), params); |
159 | } |
160 | if (eqic(uri, "/allPagesEndingWith")) { |
161 | assertNempty(q); |
162 | ret servePagesToBot(filter(list(Page), p -> ewic(p.url, q)), params); |
163 | } |
164 | |
165 | if (eqicOneOf(uri, "/postSigned", "/makePhysicalSlice", "/approveTrustRequest")) { |
166 | S text = rtrim(params.get('text)); |
167 | S key = getSignerKey(text); |
168 | if (empty(key)) ret subBot_serve500("Please include your public key"); |
169 | if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify"); |
170 | text = dropLastTwoLines(text); // drop signer + sig line |
171 | |
172 | Signer signer = uniq_sync Signer(publicKey := key); |
173 | |
174 | if (eqic(uri, "/makePhysicalSlice")) { |
175 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
176 | Page page = findPageFromParams(jsonDecodeMap(text)); |
177 | if (page == null) ret subBot_serve500("Page not found"); |
178 | ret jsonEncode(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists"); |
179 | } |
180 | |
181 | if (eqic(uri, "/postSigned")) { |
182 | new L out; |
183 | for (S line : tlft(text)) { |
184 | SS map = jsonDecodeMap(line); |
185 | new GetEntriesAndPost x; |
186 | x.signer = signer; |
187 | Page page = findOrMakePageFromParams(map); |
188 | if (page == null) continue with out.add("Page not found: " + quote(url)); |
189 | x.go(page, map); |
190 | out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
191 | } |
192 | ret serveJSON(out); |
193 | } |
194 | |
195 | if (eqic(uri, "/approveTrustRequest")) { |
196 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
197 | Signer toApprove = conceptWhere Signer(publicKey := trim(text)); |
198 | if (toApprove == null) ret subBot_serve500("Signer to approve not found"); |
199 | cset(toApprove, trusted := true, approvedBy := signer.globalID); |
200 | ret serveJSON("Approved: " + trim(text)); |
201 | } |
202 | |
203 | ret subBot_serve500("CONFUSION"); |
204 | } |
205 | |
206 | if (eqic(uri, "/post")) { |
207 | new GetEntriesAndPost x; |
208 | x.go(conceptWhereCI Page(+url), params); |
209 | ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
210 | } |
211 | if (eqic(uri, "/latestEntries")) |
212 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestEntries.objectIterator()), entryToMap(true))); |
213 | if (eqic(uri, "/latestPages")) |
214 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestCreatedPages.objectIterator()), pageToMap())); |
215 | if (eqic(uri, "/latestChangedPages")) |
216 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestChangedPages.objectIterator()), pageToMap())); |
217 | |
218 | if (eqic(uri, "/totalPageCount")) |
219 | ret serveJSON(countConcepts(Page)); |
220 | if (eqic(uri, "/physicalSliceCount")) |
221 | ret serveJSON(countConcepts(PhysicalSlice)); |
222 | if (eqic(uri, "/trustedSignersCount")) |
223 | ret serveJSON(countConcepts(Signer, trusted := true)); |
224 | |
225 | if (eqic(uri, "/valueSearch")) { |
226 | S value = params.get('value); |
227 | L<Entry> entries = conceptsWhereIC Entry(+value); |
228 | ret serveJSON_shallowLineBreaks(map(takeFirst(100, entries), entryToMap(true)); |
229 | } |
230 | |
231 | // end of bot functions |
232 | |
233 | ret subBot_serve404(); |
234 | } |
235 | |
236 | static IF1<Page, Map> pageToMap(O... _) { |
237 | bool withEntries = boolPar withEntries(_); |
238 | ret (IF1<Page, Map>) p -> { |
239 | L<Entry> entries = findBackRefs(p, Entry); |
240 | ret litorderedmap( |
241 | url := p.url, |
242 | nEntries := l(entries), |
243 | created := p.created, |
244 | modified := p._modified, |
245 | entries := !withEntries ? null : map(entries, entryToMap(false))); |
246 | }; |
247 | } |
248 | |
249 | static IF1<Entry, Map> entryToMap(bool withPage) { |
250 | ret (IF1<Entry, Map>) e -> litorderedmap( |
251 | created := e.created, |
252 | i := e.count, |
253 | key := e.key, |
254 | value := e.value, |
255 | url := withPage ? e.page->url : null, |
256 | signer := getString globalID(e.signer!)); |
257 | } |
258 | |
259 | sO servePageToBot(Page page, SS params) { |
260 | if (page == null) ret serveJSON(null); |
261 | params = asCIMap(params); |
262 | Map map = pageToMap(withEntries := valueIs1 withEntries(params)).get(page); |
263 | ret serveJSON(map); |
264 | } |
265 | |
266 | sO servePagesToBot(Iterable<Page> pages, SS params) { |
267 | ret serveJSON(map(pageToMap(), pages)); |
268 | } |
269 | |
270 | sclass GetEntriesAndPost { |
271 | L<Entry> entries; |
272 | Entry entry; |
273 | bool newEntry; |
274 | Signer signer; |
275 | |
276 | GetEntriesAndPost go(Page page, SS params) { |
277 | S key = trim(params.get('key)), value = trim(params.get('value)); |
278 | withDBLock { |
279 | entries = findBackRefs(page, Entry); |
280 | if (nempty(key) && nempty(value)) { |
281 | S ip = subBot_clientIP(); |
282 | entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); |
283 | if (entry == null) { |
284 | print("SAVING"); |
285 | Entry e = cnew Entry(+page, +key, +value, +ip, count := l(entries) + 1, +signer); |
286 | page.change(); // bump modification date |
287 | entry = e; |
288 | newEntry = true; |
289 | entries.add(entry); |
290 | dbLog("New entry", page := page.url, globalID := e.globalID, count := e.count, +key, +value); |
291 | } |
292 | } |
293 | } |
294 | |
295 | sortByFieldInPlace created(entries); |
296 | numberEntriesInConceptField count(entries); |
297 | this; |
298 | } |
299 | } |
300 | |
301 | static Page findPageFromParams(Map map) { |
302 | S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); |
303 | ret empty(url) ? null : conceptWhereCI Page(+url); |
304 | } |
305 | |
306 | static Page findOrMakePageFromParams(Map map) { |
307 | S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); |
308 | ret empty(url) ? null : uniqCI_sync Page(+url); |
309 | } |
Began life as a copy of #1023558
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1023706 |
Snippet name: | agi.blue with separate slices [dev.] |
Eternal ID of this version: | #1023706/1 |
Text MD5: | 976a5c6d5ae943f8d5c5628bfc98eb43 |
Author: | stefan |
Category: | javax / html |
Type: | JavaX module (desktop) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-07-03 17:07:17 |
Source code size: | 11840 bytes / 309 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 376 / 600 |
Referenced in: | [show references] |