summary refs log tree commit diff stats
path: root/tests/niminaction/Chapter7/Tweeter/src
diff options
context:
space:
mode:
Diffstat (limited to 'tests/niminaction/Chapter7/Tweeter/src')
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim11
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/database.nim93
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/tweeter.nim68
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/general.nim51
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/user.nim49
5 files changed, 272 insertions, 0 deletions
diff --git a/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim
new file mode 100644
index 000000000..67d9323f2
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim
@@ -0,0 +1,11 @@
+discard """
+disabled: true
+output: "Database created successfully!"
+"""
+
+import database
+
+var db = newDatabase()
+db.setup()
+echo("Database created successfully!")
+db.close()
diff --git a/tests/niminaction/Chapter7/Tweeter/src/database.nim b/tests/niminaction/Chapter7/Tweeter/src/database.nim
new file mode 100644
index 000000000..bd6667f70
--- /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.toUnix().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.high:
+      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: fromUnix(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..1b521521c
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim
@@ -0,0 +1,68 @@
+discard """
+disabled: true
+action: compile
+matrix: "--threads:off"
+"""
+
+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().utc() + 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..4abcf440d
--- /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.utc().format("HH:mm MMMM d',' yyyy")}</span>
+      <h3>${message.msg}</h3>
+    </div>
+  #end for
+</div>
+#end proc
+#
+#when true:
+#  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