-
Notifications
You must be signed in to change notification settings - Fork 6
/
hello_wiki_mongo.opa
196 lines (178 loc) · 5.8 KB
/
hello_wiki_mongo.opa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* {1 Import the high-level MongoDB API module}
*
* For the low-level API see the package stdlib.apis.mongo
*/
import stdlib.apis.{mongo}
/**
* {1 Import standard classes of bootstrap css}
*
* see http://twitter.github.com/bootstrap/
*/
import stdlib.themes.bootstrap
/**
* {1 Import templates}
*/
import stdlib.web.template
/**
* {1 Database and database interaction}
*/
/**
* The basic info. about the database and table location.
*/
type page = {
_id : string;
_rev : Bson.int32;
content : string;
}
/**
* We work at level 1, run-time type-checked storage of a collection of OPA values.
*
* The Mongo.pkg type provides convenience functions for building select and update documents.
**/
(wiki_collection,wiki_pkg) = (MongoCollection.openpkgfatal("default","db","wiki"):Mongo.pkg(page))
pageselect(v) = wiki_pkg.select(Bson.opa2doc(v))
pageupdate(v) = wiki_pkg.update(Bson.opa2doc(v))
/**
* Indexes aren't automatic in MongoDB apart from the non-removable _id index.
* Since we're searching on _rev as well, we need a separate index.
**/
_ = MongoCollection.create_index(wiki_collection, "db.wiki", Bson.opa2doc({_id=1; _rev=1}), 0)
/**
* Retrieves a json document from the database
*
* @param docid The id of the document to retrieve (arbitrary string)
* @return A Template.content
*/
get_content(docid) =
default = Template.text("This page is empty. Double-click to edit.")
extract_content(record:page) = record.content
// Order by reverse _rev to get highest numbered _rev.
orderby = {some=Bson.opa2doc({_rev=-1})}
match MongoCollection.find_one(MongoCollection.orderby(wiki_collection,orderby),pageselect({_id=docid})) with
| {success=page} ->
source = extract_content(page)
(match Template.try_parse(Template.default, source) with
| {~success} -> success
| {failure=_} -> Template.text(source))
| {failure={NotFound}} ->
default
| {~failure} ->
do jlog("hello_wiki_mongo: failure={MongoDriver.string_of_failure(failure)}")
default
/**
* Read the content associated to a topic from the database and return the
* corresponding source code.
*
* @param topic A topic (arbitrary string).
* @return If a page has been saved in for [topic], the source code for this
* page. Otherwise, the source code for the default page.
*/
@publish load_source(topic) =
Template.to_source(Template.default, get_content(topic))
/**
* Read the content associated to a topic from the database and return the
* corresponding xhtml, ready to insert.
*
* @param topic A topic (arbitrary string).
* @return If a page has been saved in for [topic], the xhtml for this
* page. Otherwise, the xhtml for the default page.
*
* Note: This function does not perform any caching.
*/
@publish load_rendered(topic) =
Template.to_xhtml(Template.default, get_content(topic))
/**
* Accept source code, save the corresponding document in the database.
*
* @param topic A topic (arbitrary string).
* @param source Source code to store at this topic. If this source code
* is syntactically valid, store the template datastructure
* corresponding to its content [Template.content].
* Otherwise, the source code is implicitly replaced by the document
* representing this raw code and this document is saved in the database.
*
* @return In case of success, the xhtml for the page that has just been
* saved. In case of failure, an error message.
*/
@publish save_source(topic, source) =
select = pageselect({_id=topic})
update = pageupdate({`$set`={content=source}; `$inc`={_rev=(1:Bson.int32)}})
// Upsert this so we create it if it isn't there
result = MongoCollection.updatee(MongoCollection.upsert(wiki_collection),select,update)
if MongoDriver.is_error(result)
then <>Error: {MongoDriver.pretty_of_result(result)}</>
else
match Template.try_parse(Template.default, source) with
| ~{success} -> Template.to_xhtml(Template.default, success)
| {failure = _} -> <>Error: {source}</>
/**
* {1 User interface}
*/
/**
* Set the user interface in edition mode.
*
* Load the source code for a topic, display an editable zone for this source code.
*
* @param topic The topic to edit.
*/
edit(topic) =
do Dom.set_value(#edit_content, load_source(topic))
do Dom.hide(#show_content)
do Dom.show(#edit_content)
do Dom.give_focus(#edit_content)
void
/**
* Set the user interface in reading mode.
*
* Save the source code for a topic (extracted from [#edit_content]),
* display the rendered version.
*
* @param topic The topic to save.
*/
save(topic) =
content = save_source(topic, Dom.get_value(#edit_content))
do Dom.transform([#show_content <- content])
do Dom.hide(#edit_content)
do Dom.show(#show_content)
void
/**
* Main user interface
*
* @param topic The topic being consulted
* @return A resource, ready to be passed to a dispatcher.
*/
display(topic) =
Resource.styled_page("About {topic}", ["/resources/css.css"],
<div class="topbar"><div class="fill"><div class="container">
<div id=#logo></div>
</div></div></div>
<div class="content container">
<div class="page-header"><h1>About {topic}</h1></div>
<div class="well" id=#show_content ondblclick={_ -> edit(topic)}>
{load_rendered(topic)}
</div>
<textarea rows="30" id=#edit_content onblur={_ -> save(topic)}></textarea>
</div>
)
/**
* {1 Main application}
*/
/**
* Dispatch requests to the user interface
*
* Note: The empty request is dispatched as if it were "Hello".
*/
start =
| {path = [] ... } ->
display("Hello")
| {~path ...} ->
display(String.capitalize(String.to_lower(String.concat("::", path))))
/**
* Statically embed a bundle of resources
*/
server = Server.of_bundle([@static_include_directory("resources")])
/**
* Launch the [start] dispatcher
*/
server = Server.simple_dispatch(start)