diff options
author | Dominik Picheta <dominikpicheta@gmail.com> | 2017-10-01 17:17:40 +0100 |
---|---|---|
committer | Dominik Picheta <dominikpicheta@gmail.com> | 2017-10-01 17:17:40 +0100 |
commit | 7889c03cbc50afaa67e1e0eedb4fdcc577913bcd (patch) | |
tree | 96f310842e9313166e69a0a4ccdd74645f1a9098 /tests/niminaction/Chapter7/Tweeter | |
parent | a585748f2747bfa9f5e9d5585a74928a9fd13dc5 (diff) | |
download | Nim-7889c03cbc50afaa67e1e0eedb4fdcc577913bcd.tar.gz |
Add tests for examples from Nim in Action.
Diffstat (limited to 'tests/niminaction/Chapter7/Tweeter')
9 files changed, 422 insertions, 0 deletions
diff --git a/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble new file mode 100644 index 000000000..0a0ffad1a --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A simple Twitter clone developed in Nim in Action." +license = "MIT" + +bin = @["tweeter"] +skipExt = @["nim"] + +# Dependencies + +requires "nim >= 0.13.1" +requires "jester >= 0.0.1" diff --git a/tests/niminaction/Chapter7/Tweeter/public/style.css b/tests/niminaction/Chapter7/Tweeter/public/style.css new file mode 100644 index 000000000..baacfaf9d --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/public/style.css @@ -0,0 +1,117 @@ +body { + background-color: #f1f9ea; + margin: 0; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} + +div#main { + width: 80%; + margin-left: auto; + margin-right: auto; +} + +div#user { + background-color: #66ac32; + width: 100%; + color: #c7f0aa; + padding: 5pt; +} + +div#user > h1 { + color: #ffffff; +} + +h1 { + margin: 0; + display: inline; + padding-left: 10pt; + padding-right: 10pt; +} + +div#user > form { + float: right; + margin-right: 10pt; +} + +div#user > form > input[type="submit"] { + border: 0px none; + padding: 5pt; + font-size: 108%; + color: #ffffff; + background-color: #515d47; + border-radius: 5px; + cursor: pointer; +} + +div#user > form > input[type="submit"]:hover { + background-color: #538c29; +} + + +div#messages { + background-color: #a2dc78; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #1a1a1a; +} + +div#messages > div { + border-left: 1px solid #869979; + border-right: 1px solid #869979; + border-bottom: 1px solid #869979; + padding: 5pt; +} + +div#messages > div > a, div#messages > div > span { + color: #475340; +} + +div#messages > div > a:hover { + text-decoration: none; + color: #c13746; +} + +h3 { + margin-bottom: 0; + font-weight: normal; +} + +div#login { + width: 200px; + margin-left: auto; + margin-right: auto; + margin-top: 20%; + + font-size: 130%; +} + +div#login span.small { + display: block; + font-size: 56%; +} + +div#newMessage { + background-color: #538c29; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #ffffff; + padding: 5pt; +} + +div#newMessage span { + padding-right: 5pt; +} + +div#newMessage form { + display: inline; +} + +div#newMessage > form > input[type="text"] { + width: 80%; +} + +div#newMessage > form > input[type="submit"] { + font-size: 80%; +} diff --git a/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim new file mode 100644 index 000000000..c7aee1b44 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim @@ -0,0 +1,6 @@ +import database + +var db = newDatabase() +db.setup() +echo("Database created successfully!") +db.close() \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/src/database.nim b/tests/niminaction/Chapter7/Tweeter/src/database.nim new file mode 100644 index 000000000..4faba3f6a --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/database.nim @@ -0,0 +1,93 @@ +import times, db_sqlite, strutils #<1> +type #<2> + Database* = ref object + db*: DbConn + + User* = object #<3> + username*: string #<4> + following*: seq[string] #<5> + + Message* = object #<6> + username*: string #<7> + time*: Time #<8> + msg*: string #<9> + +proc newDatabase*(filename = "tweeter.db"): Database = + new result + result.db = open(filename, "", "", "") + +proc close*(database: Database) = + database.db.close() + +proc setup*(database: Database) = + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS User( + username text PRIMARY KEY + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Following( + follower text, + followed_user text, + PRIMARY KEY (follower, followed_user), + FOREIGN KEY (follower) REFERENCES User(username), + FOREIGN KEY (followed_user) REFERENCES User(username) + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Message( + username text, + time integer, + msg text NOT NULL, + FOREIGN KEY (username) REFERENCES User(username) + ); + """) + +proc post*(database: Database, message: Message) = + if message.msg.len > 140: #<1> + raise newException(ValueError, "Message has to be less than 140 characters.") + + database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);", #<2> + message.username, $message.time.toSeconds().int, message.msg) #<3> + +proc follow*(database: Database, follower: User, user: User) = + database.db.exec(sql"INSERT INTO Following VALUES (?, ?);",#<2> + follower.username, user.username) + +proc create*(database: Database, user: User) = + database.db.exec(sql"INSERT INTO User VALUES (?);", user.username) #<2> + +proc findUser*(database: Database, username: string, user: var User): bool = + let row = database.db.getRow( + sql"SELECT username FROM User WHERE username = ?;", username) + if row[0].len == 0: return false + else: user.username = row[0] + + let following = database.db.getAllRows( + sql"SELECT followed_user FROM Following WHERE follower = ?;", username) + user.following = @[] + for row in following: + if row[0].len != 0: + user.following.add(row[0]) + + return true + +proc findMessages*(database: Database, usernames: seq[string], + limit = 10): seq[Message] = + result = @[] + if usernames.len == 0: return + var whereClause = " WHERE " + for i in 0 .. <usernames.len: + whereClause.add("username = ? ") + if i != <usernames.len: + whereClause.add("or ") + + let messages = database.db.getAllRows( + sql("SELECT username, time, msg FROM Message" & + whereClause & + "ORDER BY time DESC LIMIT " & $limit), + usernames) + for row in messages: + result.add(Message(username: row[0], time: fromSeconds(row[1].parseInt), msg: row[2])) diff --git a/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim new file mode 100644 index 000000000..b8a36306e --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim @@ -0,0 +1,62 @@ +import asyncdispatch, times + +import jester + +import database, views/user, views/general + +proc userLogin(db: Database, request: Request, user: var User): bool = + if request.cookies.hasKey("username"): + if not db.findUser(request.cookies["username"], user): + user = User(username: request.cookies["username"], following: @[]) + db.create(user) + return true + else: + return false + +let db = newDatabase() +routes: + get "/": + var user: User + if db.userLogin(request, user): + let messages = db.findMessages(user.following & user.username) + resp renderMain(renderTimeline(user.username, messages)) + else: + resp renderMain(renderLogin()) + + get "/@name": + cond '.' notin @"name" + var user: User + if not db.findUser(@"name", user): + halt "User not found" + let messages = db.findMessages(@[user.username]) + + var currentUser: User + if db.userLogin(request, currentUser): + resp renderMain(renderUser(user, currentUser) & renderMessages(messages)) + else: + resp renderMain(renderUser(user) & renderMessages(messages)) + + post "/follow": + var follower: User + var target: User + if not db.findUser(@"follower", follower): + halt "Follower not found" + if not db.findUser(@"target", target): + halt "Follow target not found" + db.follow(follower, target) + redirect(uri("/" & @"target")) + + post "/login": + setCookie("username", @"username", getTime().getGMTime() + 2.hours) + redirect("/") + + post "/createMessage": + let message = Message( + username: @"username", + time: getTime(), + msg: @"message" + ) + db.post(message) + redirect("/") + +runForever() diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/general.nim b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim new file mode 100644 index 000000000..0e920b1de --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim @@ -0,0 +1,51 @@ +#? stdtmpl(subsChar = '$', metaChar = '#') +#import "../database" +#import user +#import xmltree +# +#proc `$!`(text: string): string = escape(text) +#end proc +# +#proc renderMain*(body: string): string = +# result = "" +<!DOCTYPE html> +<html> + <head> + <title>Tweeter written in Nim</title> + <link rel="stylesheet" type="text/css" href="style.css"> + </head> + + <body> + ${body} + </body> + +</html> +#end proc +# +#proc renderLogin*(): string = +# result = "" +<div id="login"> + <span>Login</span> + <span class="small">Please type in your username...</span> + <form action="login" method="post"> + <input type="text" name="username"> + <input type="submit" value="Login"> + </form> +</div> +#end proc +# +#proc renderTimeline*(username: string, messages: seq[Message]): string = +# result = "" +<div id="user"> + <h1>${$!username}'s timeline</h1> +</div> +<div id="newMessage"> + <span>New message</span> + <form action="createMessage" method="post"> + <input type="text" name="message"> + <input type="hidden" name="username" value="${$!username}"> + <input type="submit" value="Tweet"> + </form> +</div> +${renderMessages(messages)} +#end proc \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/user.nim b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim new file mode 100644 index 000000000..f3791b493 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim @@ -0,0 +1,49 @@ +#? stdtmpl(subsChar = '$', metaChar = '#', toString = "xmltree.escape") +#import "../database" +#import xmltree +#import times +# +#proc renderUser*(user: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> +</div> +#end proc +# +#proc renderUser*(user: User, currentUser: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> + #if user.username notin currentUser.following: + <form action="follow" method="post"> + <input type="hidden" name="follower" value="${currentUser.username}"> + <input type="hidden" name="target" value="${user.username}"> + <input type="submit" value="Follow"> + </form> + #end if +</div> +# +#end proc +# +#proc renderMessages*(messages: seq[Message]): string = +# result = "" +<div id="messages"> + #for message in messages: + <div> + <a href="/${message.username}">${message.username}</a> + <span>${message.time.getGMTime().format("HH:mm MMMM d',' yyyy")}</span> + <h3>${message.msg}</h3> + </div> + #end for +</div> +#end proc +# +#when isMainModule: +# echo renderUser(User(username: "d0m96<>", following: @[])) +# echo renderMessages(@[ +# Message(username: "d0m96", time: getTime(), msg: "Hello World!"), +# Message(username: "d0m96", time: getTime(), msg: "Testing") +# ]) +#end when diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim new file mode 100644 index 000000000..926ca452c --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim @@ -0,0 +1,28 @@ +import database, os, times + +when isMainModule: + removeFile("tweeter_test.db") + var db = newDatabase("tweeter_test.db") + db.setup() + + db.create(User(username: "d0m96")) + db.create(User(username: "nim_lang")) + + db.post(Message(username: "nim_lang", time: getTime() - 4.seconds, + msg: "Hello Nim in Action readers")) + db.post(Message(username: "nim_lang", time: getTime(), + msg: "99.9% off Nim in Action for everyone, for the next minute only!")) + + var dom: User + doAssert db.findUser("d0m96", dom) + var nim: User + doAssert db.findUser("nim_lang", nim) + db.follow(dom, nim) + + doAssert db.findUser("d0m96", dom) + + let messages = db.findMessages(dom.following) + echo(messages) + doAssert(messages[0].msg == "99.9% off Nim in Action for everyone, for the next minute only!") + doAssert(messages[1].msg == "Hello Nim in Action readers") + echo("All tests finished successfully!") diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims new file mode 100644 index 000000000..226905fbf --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims @@ -0,0 +1,2 @@ +--path:"../src" +#switch("path", "./src") |