From 71489f4f3320306e9c920eb83a908681b7d3ab12 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 19:20:23 -0700 Subject: [PATCH 1/6] start nim rewrite --- .gitignore | 2 ++ Makefile | 5 +++++ Procfile | 1 - ponyapi.nim | 35 +++++++++++++++++++++++++++++++++++ ponyapi.nimble | 6 +++--- 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 Makefile delete mode 100644 Procfile create mode 100644 ponyapi.nim diff --git a/.gitignore b/.gitignore index 37744de..5fe4b24 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ docs/_build/ # PyBuilder target/ +ponyapi +nimcache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..50f104a --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + nim c -d:release --deadcodeElim:on --hints:off ponyapi + +run: build + ./ponyapi diff --git a/Procfile b/Procfile deleted file mode 100644 index 17353e0..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: gunicorn ponyapi:app --log-file=- diff --git a/ponyapi.nim b/ponyapi.nim new file mode 100644 index 0000000..7b422c6 --- /dev/null +++ b/ponyapi.nim @@ -0,0 +1,35 @@ +import jester +import json +import strutils + +type + Episode* = object of RootObj + ## An episode of My Little Pony: Friendship is Magic + name*: string ## Episode name + air_date*: int ## Air date in unix time + season*: int ## season number of the episode + episode*: int ## the episode number in the season + is_movie*: bool ## does this record represent a movie? + +var + episodes: seq[Episode] + +for line in lines "./fim.list": + var + ep: Episode + splitLine = line.split " " + timestr = splitLine[1] + seasonstr = splitLine[2] + episodestr = splitLine[3] + is_movie = seasonstr == "99" + name = splitLine[4 .. ^1].join " " + + ep = Episode(name: name, + air_date: timestr.parseInt, + season: seasonstr.parseInt, + episode: episodestr.parseInt, + is_movie: is_movie) + + episodes = episodes & ep + +echo episodes diff --git a/ponyapi.nimble b/ponyapi.nimble index 83b9ad6..c4ea072 100644 --- a/ponyapi.nimble +++ b/ponyapi.nimble @@ -2,9 +2,9 @@ name = "ponyapi" version = "0.1.0" author = "Christine Dodrill " -description = "PonyAPI client https://github.com/Xe/PonyAPI" +description = "PonyAPI server https://github.com/Xe/PonyAPI" license = "MIT" -srcDir = "client/nim" +bin = "ponyapi" [Deps] -Requires: "nim >= 0.10.0" +Requires: "nim >= 0.10.0, jester#head" From 162052d17b9c7c7525531bbf86e01c13112af0c1 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 19:47:00 -0700 Subject: [PATCH 2/6] Serve /all --- Makefile | 2 +- ponyapi.nim | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 50f104a..aec4956 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ build: - nim c -d:release --deadcodeElim:on --hints:off ponyapi + nim c --deadcodeElim:on --hints:off ponyapi run: build ./ponyapi diff --git a/ponyapi.nim b/ponyapi.nim index 7b422c6..1eaefad 100644 --- a/ponyapi.nim +++ b/ponyapi.nim @@ -1,3 +1,4 @@ +import asyncdispatch import jester import json import strutils @@ -32,4 +33,39 @@ for line in lines "./fim.list": episodes = episodes & ep -echo episodes +proc `%`(ep: Episode): JsonNode = + %* + { + "name": ep.name, + "air_date": ep.air_date, + "season": ep.season, + "episode": ep.episode, + "is_movie": ep.is_movie, + } + +proc `%`(eps: seq[Episode]): JsonNode = + var ret = newJArray() + + for ep in episodes: + add ret, %ep + + ret + +settings: + port = 5000.Port + bindAddr = "0.0.0.0" + +routes: + get "/": + "http://github.com/Xe/PonyAPI".uri.redirect + + get "/all": + let headers = {"Content-Type": "application/json"} + var rep = %* + { + "episodes": episodes, + } + + resp Http200, headers, pretty(rep, 4) + +runForever() From a3ea1ed922be4f9e11c83955612ae17d2d3f7b11 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 20:51:03 -0700 Subject: [PATCH 3/6] implement remaining API calls --- ponyapi.nim | 85 +++++++++++++++++++++++++++++++++++++++++++++----- ponyapi.nimble | 2 +- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/ponyapi.nim b/ponyapi.nim index 1eaefad..4294d9e 100644 --- a/ponyapi.nim +++ b/ponyapi.nim @@ -1,7 +1,10 @@ import asyncdispatch +import future import jester import json +import random import strutils +import times type Episode* = object of RootObj @@ -46,11 +49,28 @@ proc `%`(ep: Episode): JsonNode = proc `%`(eps: seq[Episode]): JsonNode = var ret = newJArray() - for ep in episodes: + for ep in eps: add ret, %ep ret +proc `%%`(ep: Episode): JsonNode = + %* + { + "episode": ep, + } + +proc `%%`(eps: seq[Episode]): JsonNode = + %* + { + "episodes": eps, + } + +let myHeaders = { + "Content-Type": "application/json", + "X-Powered-By": "Nim and Jester", +} + settings: port = 5000.Port bindAddr = "0.0.0.0" @@ -60,12 +80,63 @@ routes: "http://github.com/Xe/PonyAPI".uri.redirect get "/all": - let headers = {"Content-Type": "application/json"} - var rep = %* - { - "episodes": episodes, - } + resp Http200, myHeaders, pretty(%%episodes, 4) - resp Http200, headers, pretty(rep, 4) + get "/newest": + var + now = getTime() + ep: Episode + + for episode in episodes: + var then = times.fromSeconds(episode.air_date) + + if now < then: + ep = episode + break + + resp Http200, myHeaders, pretty(%%ep, 4) + + get "/random": + resp Http200, myHeaders, pretty(%%episodes.randomChoice(), 4) + + get "/season/@snumber": + var + season: int = @"snumber".parseInt + eps: seq[Episode] = lc[x | (x <- episodes, x.season == season), Episode] + + if eps.len == 0: + resp Http404, myHeaders, $ %* { "error": "No episodes found" } + else: + resp Http200, myHeaders, pretty(%%eps, 4) + + get "/season/@snumber/episode/@epnumber": + var + season: int = @"snumber".parseInt + enumber: int = @"epnumber".parseInt + ep: Episode + + for episode in episodes: + if episode.season == season: + if episode.episode == enumber: + ep = episode + + if ep.air_date == 0: + resp Http404, myHeaders, $ %* {"error": "No such episode"} + else: + resp Http200, myHeaders, pretty(%%ep, 4) + + get "/search": + var + query = @"q".toLower + eps: seq[Episode] + + for episode in episodes: + if episode.name.toLower.contains query: + eps = eps & episode + + if eps.len == 0: + resp Http404, myHeaders, $ %* { "error": "No episodes found" } + else: + resp Http200, myHeaders, pretty(%%eps, 4) runForever() diff --git a/ponyapi.nimble b/ponyapi.nimble index c4ea072..66eb2d7 100644 --- a/ponyapi.nimble +++ b/ponyapi.nimble @@ -7,4 +7,4 @@ license = "MIT" bin = "ponyapi" [Deps] -Requires: "nim >= 0.10.0, jester#head" +Requires: "nim >= 0.10.0, jester#head, random#head" From 651e0f60650f072ced77f3f469e97ee65a56802a Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 20:52:32 -0700 Subject: [PATCH 4/6] remove old implementation --- ponyapi.py | 105 ----------------------------------------------------- 1 file changed, 105 deletions(-) delete mode 100644 ponyapi.py diff --git a/ponyapi.py b/ponyapi.py deleted file mode 100644 index 3c6dbc6..0000000 --- a/ponyapi.py +++ /dev/null @@ -1,105 +0,0 @@ -import datetime -import os -import random - -from flask import Flask, abort, jsonify, request, redirect - -# An Episode is constructed as such: -# data Episode = Episode -# { name :: String -# , air_date :: Int -# , season :: Int -# , episode :: Int -# , is_movie :: Bool -# } -# -# Any instance of season 99 should be interpreted as a movie. -episodes = [] - -# First, open the input file and read in episodes -with open("./fim.list", "r") as f: - for line in f: - airdate, s, e, name = line.split(' ', 4)[1:] - - episode = { - "name": name[:-1], - "air_date": int(airdate), - "season": int(s), - "episode": int(e), - "is_movie": s == "99" - } - - episodes.append(episode) - -# Now, initialize Flask -app = Flask(__name__) - -@app.route("/") -def hello(): - return redirect("https://github.com/Xe/PonyAPI#ponyapi", code=302) - -@app.route("/all") -def all_episodes(): - return jsonify(episodes=episodes) - -@app.route("/newest") -def newest_episode(): - now = datetime.datetime(2006, 1, 4) - now = now.now() - - for episode in episodes: - if now.fromtimestamp(episode["air_date"]) > now: - return jsonify(episode=episode) - - abort(500) - -@app.route("/season/") -def season(number): - retEpisodes = [] - - for episode in episodes: - if str(episode["season"]) == number: - retEpisodes.append(episode) - - try: - assert len(retEpisodes) > 0 - except: - abort(404) - - return jsonify(episodes=retEpisodes) - -@app.route("/season//episode/") -def show_episode(snumber, epnumber): - for episode in episodes: - if str(episode["season"]) == snumber and str(episode["episode"]) == epnumber: - return jsonify(episode=episode) - - abort(404) - -@app.route("/random") -def show_random_ep(): - return jsonify(episode=random.choice(episodes)) - -@app.route("/search") -def search(): - retEpisodes = [] - term = request.args.get("q", "").lower() - - try: - assert term != "" - except: - abort(406) - - for episode in episodes: - if term in episode["name"].lower(): - retEpisodes.append(episode) - - try: - assert len(retEpisodes) > 0 - except: - abort(404) - - return jsonify(episodes=retEpisodes) - -if __name__ == '__main__': - app.run(host="0.0.0.0", debug=True) From e62d6877ad67a96db1791a5a0a9e74985475bc26 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 20:56:02 -0700 Subject: [PATCH 5/6] Send 406 on no search query --- ponyapi.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ponyapi.nim b/ponyapi.nim index 4294d9e..97d45e1 100644 --- a/ponyapi.nim +++ b/ponyapi.nim @@ -130,6 +130,9 @@ routes: query = @"q".toLower eps: seq[Episode] + if query == "": + halt Http406, myHeaders, $ %* { "error": "Need to specify query" } + for episode in episodes: if episode.name.toLower.contains query: eps = eps & episode From d75622b505f10773d04cb98c07cf15d43ed3338c Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 13 Aug 2015 21:04:05 -0700 Subject: [PATCH 6/6] Update Dockerfile --- Dockerfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index f5904c2..62bab10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,15 @@ -FROM python:2.7.10 +FROM coopernurse/docker-nim -RUN adduser --disabled-password --gecos '' r -ADD ./requirements.txt /app/requirements.txt -WORKDIR /app - -RUN pip install -r ./requirements.txt +EXPOSE 5000 +RUN adduser -D -g '' r +RUN chmod a+x /root/.nimble/bin/nimble ADD . /app -EXPOSE 5000 +WORKDIR /app +RUN nimble update &&\ + yes | nimble install &&\ + nim c -d:release --deadCodeElim:on ponyapi USER r -CMD gunicorn ponyapi:app --log-file=- -b 0.0.0.0:5000 -w 4 +CMD ./ponyapi