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