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/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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aec4956 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + nim c --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..97d45e1 --- /dev/null +++ b/ponyapi.nim @@ -0,0 +1,145 @@ +import asyncdispatch +import future +import jester +import json +import random +import strutils +import times + +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 + +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 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" + +routes: + get "/": + "http://github.com/Xe/PonyAPI".uri.redirect + + get "/all": + resp Http200, myHeaders, pretty(%%episodes, 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] + + if query == "": + halt Http406, myHeaders, $ %* { "error": "Need to specify query" } + + 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 83b9ad6..66eb2d7 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, random#head" 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)