d054b492d5d7f0349d8871f78924960d333150c7
[nit.git] / contrib / tnitter / src / action.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # View and controller of Tnitter
18 module action
19
20 import nitcorn
21
22 import model
23
24 redef class Session
25 # User logged in
26 var user: nullable String = null
27 end
28
29 # Main Tnitter Action
30 class Tnitter
31 super Action
32
33 var db_path = "tnitter.db"
34 var db = new DB.open(db_path)
35
36 # Header on pages served by this `Action`
37 #
38 # Keywords to `Text::replace`:
39 # * `%app_path%` is the main URL to reach this `Action`
40 # * `%nav_right%` is the pulled right part of the header, used for login form
41 var header = """
42 <nav class="navbar navbar-default" role="navigation">
43 <div class="container-fluid">
44 <div class="navbar-header">
45 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
46 <span class="sr-only">Toggle navigation</span>
47 <span class="icon-bar"></span>
48 <span class="icon-bar"></span>
49 <span class="icon-bar"></span>
50 </button>
51 <a class="navbar-brand" href="%app_path%">Tnitter</a>
52 </div>
53
54 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
55 <ul class="nav navbar-nav">
56 <li><a href="https://github.com/privat/nit/">Nit repository</a></li>
57 </ul>
58
59 <ul class="nav navbar-nav pull-right">
60 %header_right%
61 </ul>
62 </div>
63 </div>
64 </nav>"""
65
66 # Template of the pages served by this `Action`
67 #
68 # Keywords to `Text::replace`:
69 # * The `%header%`, first thing in the `<body>`
70 # * The main page `%content%` within a `<div class="container">`
71 var template = """
72 <!DOCTYPE html>
73 <html>
74 <head>
75 <title>Tnitter</title>
76 <meta charset="utf-8">
77 <meta http-equiv="X-UA-Compatible" content="IE=edge">
78 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
79
80 <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
81 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
82 <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
83 </head>
84 <body>
85
86 %header%
87
88 <div class="container">
89 %content%
90 </div>
91 </body>
92 </html>"""
93
94 redef fun answer(request, turi)
95 do
96 # Get existing session
97 var session = request.session
98
99 # Error to display on page as a dismissable panel
100 var error = null
101
102 # Login/logout
103 if turi == "/login" and request.post_args.keys.has("user") and
104 request.post_args.keys.has("pass") then
105
106 var user = request.post_args["user"].trim
107 var pass = request.post_args["pass"]
108
109 var original_user = db.check_login(user, pass)
110 if original_user != null then
111 # Log in successful
112 if session == null then session = new Session
113 session.user = original_user
114 else
115 # Check for basic requirements
116 if user.is_empty then
117 error = "Username must have at least 1 character"
118 else if user.chars.has(' ') or user.chars.has('\n') then
119 error = "Username cannot contain white spaces"
120 else if db.sign_up(user, pass) then
121 # Sign up successful
122 if session == null then session = new Session
123 session.user = user
124 else
125 # Invalid user/pass
126 error = "Invalid combination of username and password"
127 session = null
128 end
129 end
130 else if turi == "/logout" then
131 # Logging out
132 session = null
133 else if turi == "/post" and request.post_args.keys.has("text") and session != null then
134 var user = session.user
135 if user != null then
136 # Post a Tnit!
137 var text = request.post_args["text"]
138 db.post(user, text)
139
140 # Redirect the user to avoid double posting
141 var response = new HttpResponse(303)
142 response.header["Location"] = request.uri
143 return response
144 end
145 end
146
147 var login_or_out
148 var content
149 if session == null or session.user == null then
150 # Log in form in the navbar
151 login_or_out = """
152 <li>
153 <form class="navbar-form" role="form" action="login" method="POST">
154 <div class="form-group">
155 <input type="text" placeholder="Username" class="form-control" name="user">
156 </div>
157 <div class="form-group">
158 <input type="password" placeholder="Password" class="form-control" name="pass">
159 </div>
160 <button type="submit" class="btn btn-default">Log in (or sign up)</button>
161 </form>
162 </li>
163 """
164
165 # Cannot post when not logged in
166 content = ""
167 else
168 # Log out form in the navbar
169 login_or_out = """
170 <li><p class="navbar-text">Signed in as @{{{session.user.html_escape}}}</p></li>
171 <li>
172 <form class="navbar-form" role="form" action="logout" method="POST">
173 <button type="submit" class="btn btn-default">Log out</button>
174 </form>
175 </li>
176 """
177
178 # Post form
179 content = """
180 <form class="form" role="form" action="post" method="POST">
181 <div class="form-group">
182 <div class="input-group">
183 <div class="input-group-addon">Share your thoughts</div>
184 <input class="form-control" type="text" placeholder="..." name="text">
185 <span class="input-group-btn">
186 <button class="btn btn-default" type="submit">Tnit!</button>
187 </span>
188 </div><!-- /input-group -->
189 </div>
190 </form>
191 """
192 end
193
194 # Show error if any
195 var error_html
196 if error != null then
197 error_html = """
198 <div class="alert alert-danger alert-dismissible" role="alert">
199 <button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
200 {{{error}}}
201 </div>
202 """
203 else error_html = ""
204
205 # Load the last 16 Tnits
206 var posts = db.latest_posts(16)
207
208 var html_posts = new Array[String]
209 for post in posts do
210 html_posts.add "<tr><td>@{post.user.html_escape}</td><td>{post.text.html_escape}</td></tr>"
211 end
212
213 content += """
214 <div class="panel panel-default">
215 <div class="panel-heading">Latest Tnits</div>
216 <table class="table table-striped">
217 {{{html_posts.join("\n")}}}
218 </table>
219 </div>
220 </div>
221 """
222
223 # Get page from template, we replace the header first so we can replace
224 # everything on the same body afterwards
225 var body = template.
226 replace("%header%", header).
227 replace("%app_path%", request.uri.strip_extension(turi) + "/").
228 replace("%header_right%", login_or_out).
229 replace("%content%", error_html + content)
230
231 # Build response
232 var response = new HttpResponse(200)
233 response.body = body
234 response.session = session
235 return response
236 end
237 end