summary refs log tree commit diff stats
path: root/tests/niminaction/Chapter7/Tweeter
diff options
context:
space:
mode:
authorDominik Picheta <dominikpicheta@gmail.com>2017-10-01 17:17:40 +0100
committerDominik Picheta <dominikpicheta@gmail.com>2017-10-01 17:17:40 +0100
commit7889c03cbc50afaa67e1e0eedb4fdcc577913bcd (patch)
tree96f310842e9313166e69a0a4ccdd74645f1a9098 /tests/niminaction/Chapter7/Tweeter
parenta585748f2747bfa9f5e9d5585a74928a9fd13dc5 (diff)
downloadNim-7889c03cbc50afaa67e1e0eedb4fdcc577913bcd.tar.gz
Add tests for examples from Nim in Action.
Diffstat (limited to 'tests/niminaction/Chapter7/Tweeter')
-rw-r--r--tests/niminaction/Chapter7/Tweeter/Tweeter.nimble14
-rw-r--r--tests/niminaction/Chapter7/Tweeter/public/style.css117
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim6
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/database.nim93
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/tweeter.nim62
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/general.nim51
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/user.nim49
-rw-r--r--tests/niminaction/Chapter7/Tweeter/tests/database_test.nim28
-rw-r--r--tests/niminaction/Chapter7/Tweeter/tests/database_test.nims2
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")