Compare commits

...

57 Commits

Author SHA1 Message Date
Cadey Ratio a98f5fcd3a modernize nimble file 2016-04-11 00:56:11 -07:00
Cadey Ratio 0a2daa0140 why was this still there 2016-04-11 00:35:05 -07:00
Cadey Ratio b0dc06c75a more horse 2016-03-25 11:54:13 -07:00
Christine Dodrill 0575283f5c MORE HORSE 2016-03-05 10:05:13 -08:00
Christine Dodrill 5e6f321c9c Update README 2016-01-16 08:31:43 -08:00
Christine Dodrill bb55555bdd vendor the clients 2016-01-16 08:27:10 -08:00
Christine Dodrill a0fd4d1588 don't fail in nim for hiatus 2016-01-16 08:17:08 -08:00
Christine Dodrill 95cb346e20 fix the clients 2016-01-16 08:15:54 -08:00
Christine Dodrill b0bd424056 typo 2016-01-16 08:07:44 -08:00
Christine Dodrill d7a5ccb2cb add run artifacts
Closes #6
2016-01-16 08:00:22 -08:00
Christine Dodrill b4ec8adf9c Fix for Nim 0.12
Revert "fix"

This reverts commit f4bf5ebe0b.
2016-01-16 07:45:10 -08:00
Christine Dodrill 8e92e1097e fix newest/last aired routes 2015-11-28 16:57:07 -08:00
Christine Dodrill f4bf5ebe0b fix 2015-11-20 13:49:09 -08:00
Christine Dodrill e47a4da840 more episodes 2015-11-20 13:41:27 -08:00
Christine Dodrill 3e27f55a76 update episode list 2015-11-14 16:59:11 -08:00
Christine Dodrill 895f8011a7 Add IRC command as a catchall 2015-11-07 08:22:25 -08:00
Christine Dodrill 5472c5c969 TIME ZONES 2015-11-07 08:05:12 -08:00
Christine Dodrill 6b7b1c78d1 daylight savings time 2015-11-07 08:03:37 -08:00
Christine Dodrill 77ba95c811 update episode list 2015-10-15 11:50:13 -07:00
Christine Dodrill 92def355ff Reverse 5-19 and 5-20 titles
Thanks ColtonDRG in #Yayponies for catching this
2015-09-21 00:14:07 -07:00
Christine Dodrill af1521874d update air time of Friendship Games 2015-09-19 18:32:59 -07:00
Christine Dodrill d668137e95 One more 2015-09-17 06:53:22 -07:00
Christine Dodrill 2982eb2bd5 Add more ponies
http://www.equestriadaily.com/2015/09/three-new-episode-titles-and-synopsis.html
2015-09-17 06:52:23 -07:00
Christine Dodrill 375278bc12 add contributing guidelines 2015-09-08 23:34:14 -07:00
Christine Dodrill c050b65bbf Add X-API-Options documentation 2015-09-08 16:23:22 -07:00
Christine Dodrill 882b5b1551 Add new X-API-Options header for bare replies
Setting X-API-Options to bare will return unpacked values.

    $ curl --header "X-API-Options: bare" http://127.0.0.1:5000/last_aired
    {
        "name": "Do Princesses Dream of Magic Sheep?",
        "air_date": 1436628600,
        "season": 5,
        "episode": 13,
        "is_movie": false
    }
2015-09-08 15:48:06 -07:00
Christine Dodrill 791b7d59a0 Use a template for JSON rendering, etc 2015-09-08 15:20:22 -07:00
Christine Dodrill 50a6d7b5f6 fix closing bracket here 2015-08-28 16:15:06 -07:00
Christine Dodrill dc24fa6b48 Collect and report statistics about usage of the api server 2015-08-27 20:32:07 -07:00
Christine Dodrill 5d3a94bffa Show the time that the server was started in a HTTP header 2015-08-27 20:31:47 -07:00
Christine Dodrill a0688e4b55 Add Made in Manehattan 2015-08-25 20:34:18 -07:00
Christine Dodrill 6aa9ec5b18 Merge pull request #5 from akaritakai/master
Improved failure behavior and spelling corrections.
2015-08-19 15:35:55 -07:00
Justin Kaufman 8dfbfd2f2b Improved failure behavior and spelling corrections. 2015-08-19 17:10:53 -05:00
Christine Dodrill 0b3e015482 fix gitignore and permissions 2015-08-18 16:40:45 -07:00
Christine Dodrill 50c2cbed2c Merge pull request #4 from akaritakai/master
Java Client
2015-08-18 14:34:31 -07:00
Justin Kaufman 20cad274e6 Added Java client 2015-08-18 15:55:35 -05:00
Christine Dodrill 956d57a9dd update nim docs 2015-08-17 15:01:47 -07:00
Christine Dodrill 34682afd1e Update git rev checking 2015-08-17 14:47:34 -07:00
Christine Dodrill e2fd239e37 Update Python API client 2015-08-17 14:46:04 -07:00
Christine Dodrill 0cc73a2e95 Update Nim API client 2015-08-17 14:36:32 -07:00
Christine Dodrill 045f69d53a Update Go API client 2015-08-17 14:36:24 -07:00
Christine Dodrill b5d36da2b6 Add /last_aired route
For @ask-compu
2015-08-17 14:32:05 -07:00
Christine Dodrill 24ef6f4c69 Show git commit hash of server in replies 2015-08-17 14:30:38 -07:00
Christine Dodrill 67c484ee19 split off episode manipulation code into its own module 2015-08-15 10:59:53 -07:00
Christine Dodrill d80166a420 List comprehensions are good, let's use them 2015-08-14 14:21:43 -07:00
Christine Dodrill 813e1164c1 time each test suite 2015-08-14 07:51:12 -07:00
Christine Dodrill 9489789334 Use `result`
It is idiomatic nim
2015-08-13 21:19:44 -07:00
Christine Dodrill c7af9e9bb2 Tests: be less verbose 2015-08-13 21:18:36 -07:00
Christine Dodrill 577de316ac update gitattributes 2015-08-13 21:10:34 -07:00
Christine Dodrill 3ce8c3b833 static -> public, use bash 2015-08-13 21:09:02 -07:00
Christine Dodrill 97dbe29bca Merge pull request #2 from Xe/ref/nim-rewrite
Rewrite the server side in Nim
2015-08-13 21:06:32 -07:00
Christine Dodrill d75622b505 Update Dockerfile 2015-08-13 21:04:05 -07:00
Christine Dodrill e62d6877ad Send 406 on no search query 2015-08-13 20:56:02 -07:00
Christine Dodrill 651e0f6065 remove old implementation 2015-08-13 20:52:32 -07:00
Christine Dodrill a3ea1ed922 implement remaining API calls 2015-08-13 20:51:03 -07:00
Christine Dodrill 162052d17b Serve /all 2015-08-13 19:47:00 -07:00
Christine Dodrill 71489f4f33 start nim rewrite 2015-08-13 19:20:23 -07:00
37 changed files with 1231 additions and 180 deletions

3
.gitattributes vendored
View File

@ -1 +1,2 @@
static/* linguist-vendored
public/static/* linguist-vendored
client/* linguist-vendored

2
.gitignore vendored
View File

@ -59,3 +59,5 @@ docs/_build/
# PyBuilder
target/
ponyapi
nimcache

View File

@ -1,14 +1,17 @@
FROM python:2.7.10
FROM coopernurse/docker-nim
RUN adduser --disabled-password --gecos '' r
ADD ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN apk update && apk add bash
RUN pip install -r ./requirements.txt
EXPOSE 5000
RUN adduser -D -g '' r
RUN chmod a+x /opt/Nim/bin/nim
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

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
build:
nim c --deadcodeElim:on --hints:off ponyapi
run: build
./ponyapi

View File

@ -1 +0,0 @@
web: gunicorn ponyapi:app --log-file=-

View File

@ -35,15 +35,19 @@ Clients
- [Go](https://godoc.org/github.com/Xe/PonyAPI/client/go)
- [Nim](https://github.com/Xe/PonyAPI/blob/master/client/nim/ponyapi.nim) [Docs](http://ponyapi.apps.xeserv.us/static/nim.html)
- [Python](https://github.com/Xe/PonyAPI/blob/master/client/python/ponyapi.py)
- [Java](https://github.com/Xe/PonyAPI/tree/master/client/java)
Routes
------
The canonical route base for PonyAPI is http://ponyapi.apps.xeserv.us. Example
usage:
The canonical route base for PonyAPI is `https://ponyapi.apps.xeserv.us`. This
now supports HTTP/2.0 using [Caddy](https://caddyserver.com) and SSL using
[Let's Encrypt](https://letsencrypt.org/). If you get SSL errors, please be
sure your system certificate lists are up to date.
Example usage:
```console
$ curl http://ponyapi.apps.xeserv.us/season/1/episode/1
$ curl https://ponyapi.apps.xeserv.us/season/1/episode/1
{
"episode": {
"air_date": 1286735400,
@ -55,6 +59,31 @@ $ curl http://ponyapi.apps.xeserv.us/season/1/episode/1
}
```
Bare Replies
------------
As of [882b5b1](https://github.com/Xe/PonyAPI/commit/882b5b155157d3a3c9e329fffcf7ff3fdf64d4ee),
PonyAPI will accept an `X-API-Options` header that when set to `bare` will
return the API replies without the `episode` or `episodes` header.
Functionality is otherwise unchanged, however an error will still be shown if
something goes wrong, and that will parse differently. This API will return
`200` if and **only** if everything went to plan.
An example:
```console
$ curl --header "X-API-Options: bare" https://ponyapi.apps.xeserv.us/last_aired
{
"name": "Do Princesses Dream of Magic Sheep?",
"air_date": 1436628600,
"season": 5,
"episode": 13,
"is_movie": false
}
```
This will also be triggered if you set the query parameter `options` to `bare`.
### `/all`
Returns all information about all episodes. This returns an array of Episode
@ -64,13 +93,18 @@ objects as defined above.
Returns the episode of My Little Pony: Friendship is Magic that will air next.
### `/last_aired`
Returns the episode of My Little Pony: Friendship is Magic that most recently
aired.
### `/season/<number>`
Returns all information about episodes in the given season number or a `404`
reply if no episodes could be found. To get all information about the movies
shown, set the season as `99`.
### `/season/<number>/episode/<number`
### `/season/<number>/episode/<number>`
Returns all information about the episode with the given season and episode
number. If the episode cannot be found, this will return a `404`.
@ -85,3 +119,10 @@ This must be given a query paramater `q` containing the text to search for. Not
including this will return a `406` reply. This will search the list of episode
records for any episodes whose names match the given search terms. This is
case-insensitive. If no episodes can be found, this will return a `404` reply.
Contributing
------------
Contributions will be judged by their technical merit. No politics on project forums.
All code is licensed under the MIT license.

View File

@ -9,15 +9,21 @@ import (
)
const (
endpoint = "http://ponyapi.apps.xeserv.us"
endpoint = "https://ponyapi.apps.xeserv.us"
)
func getJson(fragment string) (data []byte, err error) {
resp, err := http.Get(endpoint + fragment)
c := &http.Client{}
req, err := http.NewRequest("GET", endpoint+fragment, nil)
if err != nil {
return nil, err
}
//req.Header.Add("X-API-Options", "bare")
resp, err := c.Do(req)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
@ -61,6 +67,12 @@ func Newest() (*Episode, error) {
return getEpisode("/newest")
}
// LastAired returns information on the most recently aried episode
// or an error.
func LastAired() (*Episode, error) {
return getEpisode("/last_aired")
}
// Random returns information on a random episode.
func Random() (*Episode, error) {
return getEpisode("/random")

View File

@ -4,6 +4,15 @@ import "testing"
func TestNewestEpisode(t *testing.T) {
ep, err := Newest()
if err != nil {
// t.Fatal(err)
}
t.Logf("%#v", ep)
}
func TestLastAiredEpisode(t *testing.T) {
ep, err := LastAired()
if err != nil {
t.Fatal(err)
}

View File

@ -1,6 +1,5 @@
#!/bin/bash
set -e
set -x
go test -v .
go test .

1
client/java/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.gradle

62
client/java/README.md Normal file
View File

@ -0,0 +1,62 @@
PonyAPI Java Client
===================
A Java client for accessing PonyAPI. Requires Java 8.
Example Usage
-------------
```java
package us.xeserv.examples;
import us.xeserv.ponyapi.Episode;
import us.xeserv.ponyapi.PonyApiClient;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
public class PonyApiExample {
public static void main(String[] args) throws IOException {
// Initialize a client with a custom API host
PonyApiClient client = new PonyApiClient("some.fqdn.here"); // defaults to port 80
client = new PonyApiClient("some.fqdn.here", 8080); // with a custom port number
// Initialize a client using http://ponyapi.apps.xeserv.us/ as the API host
client = new PonyApiClient();
// Get a list of all the episodes
List<Episode> allEpisodes = client.all();
// Get the newest episode
Episode newestEpisode = client.newest();
// Get information about an episode
String name = newestEpisode.name;
Instant airDate = newestEpisode.airDate;
int seasonNumber = newestEpisode.season;
int episodeNumber = newestEpisode.episode;
boolean isMovie = newestEpisode.isMovie;
// Get all episodes in a season
List<Episode> season = client.getSeason(1);
// Get all movies
List<Episode> movies = client.getMovies();
// Get a specific episode by season and episode number
Episode specificEpisode = client.getEpisode(1, 13);
// Get a specific movie by movie number
Episode specificMovie = client.getMovie(1);
// Get a random movie or movie number
Episode random = client.random();
// Get a list of all episodes and movies matching a query
List<Episode> queryResults = client.search("Owl's Well"); // returns Owl's Well That Ends Well
}
}
```

19
client/java/build.gradle Normal file
View File

@ -0,0 +1,19 @@
group 'us.xeserv'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "com.google.code.gson:gson:+"
compile "org.apache.httpcomponents:httpclient:+"
compile "org.apache.httpcomponents:fluent-hc:+"
compile "com.google.guava:guava:+"
testCompile "junit:junit:+"
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Aug 18 15:13:15 CDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip

164
client/java/gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@ -0,0 +1,84 @@
package us.xeserv.ponyapi;
import java.time.Instant;
import java.util.Objects;
/**
* The {@code Episode} class represents My Little Pony episode or movie information returned by PonyAPI.
* <p>
* Episodes are immutable; they represent data returned from the API and cannot be changed after they are created.
* <p>
* Movie data stored in an {@code Episode} object are referred to by the season number 99. Their episode number refers
* to the order of their release starting with 1.
*
* @author Justin Kaufman
* @since 1.0
*/
public class Episode {
/**
* The name of the episode or movie.
*/
public final String name;
/**
* The season of the episode, or 99 if it is a movie.
*/
public final int season;
/**
* The episode or movie number.
*/
public final int episode;
/**
* The air date.
*/
public final Instant airDate;
/**
* True if this episode represents a movie, false otherwise.
*/
public final boolean isMovie;
protected Episode(String name, int season, int episode, long airDate, boolean isMovie) {
this.name = name;
this.season = season;
this.episode = episode;
this.airDate = Instant.ofEpochSecond(airDate);
this.isMovie = isMovie;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Episode rhs = (Episode) obj;
return Objects.equals(name, rhs.name)
&& Objects.equals(season, rhs.season)
&& Objects.equals(episode, rhs.episode)
&& Objects.equals(airDate, rhs.airDate)
&& Objects.equals(isMovie, rhs.isMovie);
}
@Override
public int hashCode() {
return Objects.hash(name, season, episode, airDate, isMovie);
}
@Override
public String toString() {
return "{\n"
+ "\t\"name\": \"" + name + "\",\n"
+ "\t\"air_date\": " + airDate.getEpochSecond() + ",\n"
+ "\t\"season\": " + season + ",\n"
+ "\t\"episode\": " + episode + ",\n"
+ "\t\"is_movie\": " + isMovie + "\n"
+ "}";
}
}

View File

@ -0,0 +1,61 @@
package us.xeserv.ponyapi;
import com.google.common.base.Strings;
import com.google.gson.*;
import java.util.ArrayList;
import java.util.List;
class JsonDecoder {
private static final Gson gson;
static {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Episode.class, (JsonDeserializer<Episode>) (json, typeOfT, context) -> {
JsonObject jsonObject = json.getAsJsonObject();
return new Episode(
jsonObject.get("name").getAsString(),
jsonObject.get("season").getAsInt(),
jsonObject.get("episode").getAsInt(),
jsonObject.get("air_date").getAsLong(),
jsonObject.get("is_movie").getAsBoolean()
);
});
gson = gsonBuilder.create();
}
protected static Episode fromJson(String json) {
if (Strings.isNullOrEmpty(json)) {
return null;
}
try {
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonObject payload = wrapper.get("episode").getAsJsonObject();
return gson.fromJson(payload, Episode.class);
} catch (Exception ignored) {
// TODO: Logging for parse errors or passing a general parse exception
}
return null;
}
protected static List<Episode> listFromJson(String json) {
if (Strings.isNullOrEmpty(json)) {
return null;
}
List<Episode> list = new ArrayList<>();
try {
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonArray payload = wrapper.get("episodes").getAsJsonArray();
for (JsonElement episode : payload) {
list.add(gson.fromJson(episode, Episode.class));
}
} catch (Exception ignored) {
// TODO: Logging for parse errors or passing a general parse exception
}
if (list.isEmpty()) {
return null;
}
return list;
}
}

View File

@ -0,0 +1,170 @@
package us.xeserv.ponyapi;
import com.google.common.base.Strings;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
/**
* The {@code PonyApiClient} class provides an interface between Java code that uses this library and PonyAPI.
* <p>
* This client is able to retrieve information about the show My Little Pony: Friendship is Magic.
*
* @author Justin Kaufman
* @since 1.0
*/
public class PonyApiClient {
private final String host;
/**
* The default constructor initializes a client instance that uses http://ponyapi.apps.xeserv.us/ as the API source.
*/
public PonyApiClient() {
this("ponyapi.apps.xeserv.us");
}
/**
* Accepts a FQDN and port running the PonyAPI service on a specified port.
* @param host a FQDN running the PonyAPI service
* @param port the port the PonyAPI service is running on
*/
public PonyApiClient(String host, int port) {
this.host = host + ":" + port;
}
/**
* Accepts a FQDN running the PonyAPI service on port 80.
* @param host a FQDN running the PonyAPI service
*/
public PonyApiClient(String host) {
this.host = host;
}
/**
* Returns a list of all episodes and movies.
* @return a list of all episodes and movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> all() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/all")));
}
/**
* Returns the newest episode or movie aired.
* @return the newest episode or movie aired
* @throws IOException if there is an error accessing the service
*/
public Episode newest() throws IOException {
return JsonDecoder.fromJson(asJson(get("/newest")));
}
/**
* Returns the episode or movie that most recently aired.
* @return the episode or movie that most recently aired
* @throws IOException if there is an error accessing the service
*/
public Episode lastAired() throws IOException {
return JsonDecoder.fromJson(asJson(get("/last_aired")));
}
/**
* Returns all information about episodes in a given season number.
* @param season the season number
* @return all information about episodes in a given season number
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getSeason(int season) throws IOException {
HttpResponse response = get("/season/" + season);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.listFromJson(asJson(response));
}
/**
* Returns all information about movies.
* @return all information about movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getMovies() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/season/99")));
}
/**
* Returns all information about the episode with the given season and episode number.
* @param season the season number
* @param episode the episode number
* @return all information about the episode with the given season and episode number
* @throws IOException if there is an error accessing the service
*/
public Episode getEpisode(int season, int episode) throws IOException {
HttpResponse response = get("/season/" + season + "/episode/" + episode);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns all information about the movie with the given movie number.
* @param movie the movie number
* @return all information about the movie with the given movie number
* @throws IOException if there is an error accessing the service
*/
public Episode getMovie(int movie) throws IOException {
HttpResponse response = get("/season/99/episode/" + movie);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns a random episode or movie from the record.
* @return a random episode or movie from the record
* @throws IOException if there is an error accessing the service
*/
public Episode random() throws IOException {
String json = asJson(get("/random"));
return JsonDecoder.fromJson(json);
}
/**
* Returns a list of episodes which contain the query in its name.
* @param query a case-independent, non-empty {@code String}
* @return a list of episodes which contain the query in its name
* @throws IOException if there is an error accessing the service
* @throws IllegalArgumentException if the query is null or empty
*/
public List<Episode> search(String query) throws IOException {
if (Strings.isNullOrEmpty(query)) {
throw new IllegalArgumentException("A search query is required.");
}
HttpResponse response = get("/search?q=" + URLEncoder.encode(query, "UTF-8"));
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.listFromJson(asJson(response));
}
private HttpResponse get(String path) throws IOException {
return Request.Get("https://" + host + path)
.userAgent("PonyApi Java Client 1.0")
.execute()
.returnResponse();
}
private String asJson(HttpResponse response) throws IOException {
return EntityUtils.toString(response.getEntity());
}
private int statusCode(HttpResponse response) throws IOException {
return response.getStatusLine().getStatusCode();
}
}

View File

@ -0,0 +1,109 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class JsonDecoderTest {
@Test
public void malformedInputTest() {
assertNull(JsonDecoder.fromJson(null));
assertNull(JsonDecoder.fromJson(""));
assertNull(JsonDecoder.fromJson("{ \"error\": { \"msg\": \"some error\", \"code\": 404 }"));
}
@Test
public void episodeTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\"air_date\": 1286735400,\n"
+ "\t\t\"season\": 1,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": false\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertEquals(false, episode.isMovie);
}
@Test
public void movieTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\"air_date\": 1371340800,\n"
+ "\t\t\"season\": 99,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": true\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Equestria Girls", episode.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate);
assertEquals(99, episode.season);
assertEquals(1, episode.episode);
assertEquals(true, episode.isMovie);
}
@Test
public void multipleMalformedInputTest() {
assertNull(JsonDecoder.listFromJson(null));
assertNull(JsonDecoder.listFromJson(""));
assertNull(JsonDecoder.listFromJson("{ \"error\": { \"msg\": \"some error\", \"code\": 404 }"));
}
@Test
public void multipleTest() {
String json = "{\n"
+ "\t\"episodes\": [\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\t\"air_date\": 1286735400,\n"
+ "\t\t\t\"season\": 1,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": false\n"
+ "\t\t},\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\t\"air_date\": 1371340800,\n"
+ "\t\t\t\"season\": 99,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": true\n"
+ "\t\t}\n"
+ "\t]\n"
+ "}";
List<Episode> episodes = JsonDecoder.listFromJson(json);
assertNotNull(episodes);
assertEquals(2, episodes.size());
Episode episode1 = episodes.get(0);
assertEquals("Friendship is Magic Part 1", episode1.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode1.airDate);
assertEquals(1, episode1.season);
assertEquals(1, episode1.episode);
assertEquals(false, episode1.isMovie);
Episode episode2 = episodes.get(1);
assertEquals("Equestria Girls", episode2.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode2.airDate);
assertEquals(99, episode2.season);
assertEquals(1, episode2.episode);
assertEquals(true, episode2.isMovie);
}
}

View File

@ -0,0 +1,87 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.*;
public class PonyApiClientTest {
private PonyApiClient client = new PonyApiClient();
@Test
public void allTest() throws Exception {
List<Episode> list = client.all();
assertFalse(list.isEmpty());
}
@Test
public void newestTest() throws IOException {
Episode episode = client.newest();
assertNotNull(episode);
}
@Test
public void lastAiredTest() throws IOException {
Episode episode = client.lastAired();
assertNotNull(episode);
}
@Test
public void getSeasonTest() throws IOException {
List<Episode> list = client.getSeason(98);
assertNull(list);
list = client.getSeason(1);
assertEquals(26, list.size());
}
@Test
public void getMoviesTest() throws IOException {
List<Episode> list = client.getMovies();
assertFalse(list.isEmpty());
}
@Test
public void getEpisodeTest() throws IOException {
Episode episode = client.getEpisode(98, 1);
assertNull(episode);
episode = client.getEpisode(1, 1);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertFalse(episode.isMovie);
}
@Test
public void getMovieTest() throws IOException {
Episode movie = client.getMovie(98);
assertNull(movie);
movie = client.getMovie(1);
assertEquals("Equestria Girls", movie.name);
assertEquals(Instant.ofEpochSecond(1371340800), movie.airDate);
assertEquals(99, movie.season);
assertEquals(1, movie.episode);
assertTrue(movie.isMovie);
}
@Test
public void randomTest() throws IOException {
Episode episode = client.random();
assertNotNull(episode);
}
@Test
public void searchTest() throws IOException {
List<Episode> episodes = client.search("No Results");
assertNull(episodes);
episodes = client.search("Owl's Well");
assertEquals(1, episodes.size());
Episode episode = episodes.get(0);
assertEquals("Owl's Well That Ends Well", episode.name);
}
}

5
client/java/test.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
./gradlew test

View File

@ -14,7 +14,7 @@ type
is_movie*: bool ## does this record represent a movie?
const
API_ENDPOINT: string = "http://ponyapi.apps.xeserv.us"
API_ENDPOINT: string = "https://ponyapi.apps.xeserv.us"
proc getJson(endpoint: string): json.JsonNode =
## get the HTTP body for the API base endpoint catted with the specific endpoint
@ -44,6 +44,10 @@ proc newest*(): Episode =
## returns information on the newest episode of My Little Pony: Friendship is Magic.
getJson("/newest")["episode"].newEpisodeFromNode
proc last_aired*(): Episode =
## returns information on the most recently aired episode of My Little Pony: Friendship is Magic.
getJson("/last_aired")["episode"].newEpisodeFromNode
proc random*(): Episode =
## returns information on a random episode.
getJson("/random")["episode"].newEpisodeFromNode
@ -72,10 +76,18 @@ when isMainModule:
suite "ponyapi tests":
var ep: Episode
test "Newest episode lookup":
test "newest episode lookup":
try:
ep = newest()
except:
echo getCurrentExceptionMsg()
#fail
test "last aired episode lookup":
try:
ep = last_aired()
except:
echo getCurrentExceptionMsg()
fail

View File

@ -1,6 +1,5 @@
#!/bin/bash
set -e
set -x
nim c -r ponyapi
nim c -d:ssl --hints:off -r ponyapi

View File

@ -28,7 +28,7 @@ Available methods:
search(query) -> return all episodes that have query in the title
"""
API_ENDPOINT = "http://ponyapi.apps.xeserv.us"
API_ENDPOINT = "https://ponyapi.apps.xeserv.us"
# _base_get :: Text -> Maybe [Text] -> (Maybe [Text] -> IO (Either Episode [Episode]))
# _base_get takes a text, a splatted list of texts and returns a function such that
@ -66,6 +66,9 @@ all_episodes = _base_get("/all")
# newest :: IO Episode
newest = _base_get("/newest")
# last_aired :: IO Episode
last_aired = _base_get("/last_aired")
# random :: IO Episode
random = _base_get("/random")
@ -85,9 +88,9 @@ def search(query):
return r.json()["episodes"]
# last_aired :: IO Episode
# last_aired_old :: IO Episode
# TODO: Does not know how to wrap around seasons, fix this
def last_aired():
def last_aired_old():
new = newest()
if new[u"air_date"] > int(time.time()):

View File

@ -3,7 +3,10 @@ import unittest
class TestPonyAPI(unittest.TestCase):
def test_newest(self):
ponyapi.newest()
try:
ponyapi.newest()
except:
print "probably on hiatus"
def test_all_episodes(self):
assert len(ponyapi.all_episodes()) > 0

View File

@ -1,6 +1,5 @@
#!/bin/bash
set -e
set -x
python ./test.py

3
client/test.sh vendored
View File

@ -1,12 +1,11 @@
#!/bin/bash
set -e
set -x
for client in *
do
if [ -d "$client" ]
then
(cd "$client" && ./test.sh)
time (cd "$client" && ./test.sh)
fi
done

44
episode.nim Normal file
View File

@ -0,0 +1,44 @@
import json
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?
proc `%`*(ep: Episode): JsonNode =
## Convert an Episode record to a 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 =
## Convert a sequence of episodes to a JsonNode
result = newJArray()
for ep in eps:
add result, %ep
proc newEpisodeFromNode*(data: json.JsonNode): Episode =
## Convert a json node into an episode object
Episode(name: data["name"].getStr,
air_date: data["air_date"].getNum.int,
season: data["season"].getNum.int,
episode: data["episode"].getNum.int,
is_movie: data["is_movie"].getBVal)
proc newEpisodeListFromNode*(data: json.JsonNode): seq[Episode] =
## Convert a json array into a sequence of episode objects
var ret: seq[Episode]
for item in data.items():
ret = ret & item.newEpisodeFromNode
return ret

View File

@ -104,6 +104,23 @@ FIM 1436023800 5 12 Amending Fences
FIM 1436628600 5 13 Do Princesses Dream of Magic Sheep?
FIM 1442071800 5 14 Canterlot Boutique
FIM 1442676600 5 15 Rarity Investigates!
FIM 1443281400 5 16 Made in Manehattan
FIM 1443886200 5 17 Brotherhooves Social
FIM 1444491000 5 18 Crusaders of the Lost Mark
FIM 1445095800 5 19 The One Where Pinkie Pie Knows
FIM 1445700600 5 20 Hearthbreakers
FIM 1446305400 5 21 Scare Master
FIM 1446913800 5 22 What About Discord
FIM 1447518600 5 23 The Hooffields and McColts
FIM 1448123400 5 24 The Mane Attraction
FIM 1448726400 5 25 The Cutie Re-Mark Part 1
FIM 1448728200 5 26 The Cutie Re-Mark Part 2
FIM 1459004400 6 1 The Crystalling Part 1
FIM 1459006200 6 2 The Crystalling Part 2
FIM 1459611000 6 3 The Gift of the Maud Pie
FIM 1460215800 6 4 On Your Marks
FIM 1460820600 6 5 Gauntlet of Fire
FIM 1462030200 6 6 No Second Prances
FIM 1371340800 99 1 Equestria Girls
FIM 1411862400 99 2 Equestria Girls - Rainbow Rocks
FIM 1443277800 99 3 Equestria Girls - Friendship Games
FIM 1443321000 99 3 Equestria Girls - Friendship Games

189
ponyapi.nim Normal file
View File

@ -0,0 +1,189 @@
import asyncdispatch
import episode
import future
import jester
import json
import os
import random
import stats
import strutils
import times
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 =
## Pack an episode for PonyAPI clients.
%*
{
"episode": ep,
}
proc `%%`(eps: seq[Episode]): JsonNode =
## Pack a sequence of episodes to a JsonNode for PonyAPI clients.
%*
{
"episodes": eps,
}
proc `%%`(why: string): JsonNode =
## Make an error object
%*
{
"error": why
}
proc `%`(why: string): JsonNode =
%%why
template httpReply(code, body: expr): expr =
## Make things a lot simpler for replies, etc.
if request.headers.getOrDefault("X-API-Options") == "bare" or @"options" == "bare":
# New "bare" reply format, easier to scrape, etc.
resp code, myHeaders, pretty(%body, 4)
else:
resp code, myHeaders, pretty(%%body, 4)
let myHeaders = {
"Content-Type": "application/json",
"X-Powered-By": "Nim and Jester",
"X-Git-Hash": getEnv("GIT_REV"),
"X-Server-Epoch": $ getTime().toSeconds().int,
}
settings:
port = 5000.Port
bindAddr = "0.0.0.0"
routes:
get "/":
"http://github.com/Xe/PonyAPI".uri.redirect
get "/all":
stats.all.success.inc
httpReply Http200, episodes
get "/newest":
var
now = getTime()
ep: Episode
for episode in episodes:
var then = times.fromSeconds(episode.air_date)
if now < then:
ep = episode
break
if ep.season == 0:
stats.newest.fails.inc
halt Http404, "No new episode found, hiatus?"
stats.newest.success.inc
httpReply Http200, ep
get "/random":
stats.newest.success.inc
httpReply Http200, episodes.randomChoice()
get "/last_aired":
var
#now = getTime()
ep: Episode
for epid, episode in pairs[Episode](episodes):
# XXX HACK PLEASE FIX
if episode.season == 5 and episode.episode == 26:
ep = episode
stats.lastAired.success.inc
httpReply Http200, ep
get "/season/@snumber":
var
season: int = @"snumber".parseInt
eps: seq[Episode] = lc[x | (x <- episodes, x.season == season), Episode]
if eps.len == 0:
stats.seasonLookup.fails.inc
httpReply Http404, "No episodes found"
else:
stats.seasonLookup.success.inc
httpReply Http200, eps
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 @"format" == "irccmd":
let
irccmd = "/cs episode del $1 $2\n/cs episode add $1 $2 $3 $4" % [$ep.season, $ep.episode, $ep.air_date, ep.name]
echo irccmd
if ep.air_date == 0:
stats.episodeLookup.fails.inc
httpReply Http404, "Not found"
else:
stats.episodeLookup.success.inc
httpReply Http200, ep
get "/search":
var
query = @"q".toLower
if query == "":
stats.search.fails.inc
halt Http406, myHeaders, pretty(%%"Need to specify a query", 4)
var
eps: seq[Episode] =
lc[x | (x <- episodes, x.name.toLower.contains query), Episode]
if eps.len == 0:
stats.search.fails.inc
httpReply Http404, "No episodes found"
else:
stats.search.success.inc
httpReply Http200, eps
get "/_stats":
resp Http200, myHeaders, pretty(%*
[
stats.all,
stats.newest,
stats.random,
stats.lastAired,
stats.seasonLookup,
stats.episodeLookup,
stats.search,
], 4)
when isMainModule:
runForever()
else:
quit "This should not be called outside of being the main module"

View File

@ -1,10 +1,11 @@
[Package]
name = "ponyapi"
version = "0.1.0"
author = "Christine Dodrill <xena@yolo-swag.com>"
description = "PonyAPI client https://github.com/Xe/PonyAPI"
license = "MIT"
srcDir = "client/nim"
# Package
[Deps]
Requires: "nim >= 0.10.0"
version = "0.2.0"
author = "Cadey Dodrill"
description = "PonyAPI server https://github.com/Xe/PonyAPI"
license = "MIT"
bin = @["ponyapi"]
# Dependencies
requires "nim >= 0.13.0", "jester#head", "random#head", "sam#head", "jsmn#head"

View File

@ -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/<number>")
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/<snumber>/episode/<epnumber>")
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)

View File

@ -489,7 +489,7 @@ pre {
box-sizing: border-box;
min-width: calc(100% - 19.5px);
padding: 9.5px;
margin: 0 10px 0px 10px;
margin: 0.25em 10px 0.25em 10px;
font-size: 14px;
line-height: 20px;
white-space: pre !important;
@ -892,6 +892,10 @@ div.align-right {
/* div.align-center * { */
/* text-align: left } */
ul.simple > li {
margin-bottom: 0.5em }
ol.simple, ul.simple {
margin-bottom: 1em; }
@ -1160,11 +1164,11 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<ul class="simple simple-toc-section">
<li><a class="reference" href="#Episode"
title="Episode = object of RootObj
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?"><wbr />Episode</a></li>
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?"><wbr />Episode</a></li>
</ul>
</li>
@ -1173,6 +1177,8 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<ul class="simple simple-toc-section">
<li><a class="reference" href="#newest,"
title="newest(): Episode"><wbr />newest</a></li>
<li><a class="reference" href="#last_aired,"
title="last_aired(): Episode"><wbr />last_<wbr />aired</a></li>
<li><a class="reference" href="#random,"
title="random(): Episode"><wbr />random</a></li>
<li><a class="reference" href="#get_episode,int,int"
@ -1201,11 +1207,11 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
<h1><a class="toc-backref" href="#7">Types</a></h1>
<dl class="item">
<dt id="Episode"><a name="Episode"></a><pre><span class="Identifier">Episode</span> <span class="Other">=</span> <span class="Keyword">object</span> <span class="Keyword">of</span> <span class="Identifier">RootObj</span>
<span class="Identifier">name</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">string</span> <span class="Comment">## Episode name</span>
<span class="Identifier">air_date</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## Air date in unix time</span>
<span class="Identifier">season</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## season number of the episode</span>
<span class="Identifier">episode</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## the episode number in the season</span>
<span class="Identifier">is_movie</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">bool</span> <span class="Comment">## does this record represent a movie?</span>
<span class="Identifier">name</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">string</span> <span class="Comment">## Episode name</span>
<span class="Identifier">air_date</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## Air date in unix time</span>
<span class="Identifier">season</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## season number of the episode</span>
<span class="Identifier">episode</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">int</span> <span class="Comment">## the episode number in the season</span>
<span class="Identifier">is_movie</span><span class="Operator">*</span><span class="Other">:</span> <span class="Identifier">bool</span> <span class="Comment">## does this record represent a movie?</span>
</pre></dt>
<dd>
An episode of My Little Pony: Friendship is Magic
@ -1216,45 +1222,53 @@ An episode of My Little Pony: Friendship is Magic
<div class="section" id="12">
<h1><a class="toc-backref" href="#12">Procs</a></h1>
<dl class="item">
<dt id="newest"><a name="newest,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">newest</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span>
<span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dt id="newest"><a name="newest,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">newest</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span>
<span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
returns information on the newest episode of My Little Pony: Friendship is Magic.
</dd>
<dt id="random"><a name="random,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">random</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span>
<span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dt id="last_aired"><a name="last_aired,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">last_aired</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span>
<span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
returns information on the most recently aired episode of My Little Pony: Friendship is Magic.
</dd>
<dt id="random"><a name="random,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">random</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span>
<span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
returns information on a random episode.
</dd>
<dt id="get_episode"><a name="get_episode,int,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_episode</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">,</span> <span class="Identifier">episode</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<dt id="get_episode"><a name="get_episode,int,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_episode</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">,</span> <span class="Identifier">episode</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Episode</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
return an arbitrary episode by season, episode pair.
</dd>
<dt id="all_episodes"><a name="all_episodes,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">all_episodes</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dt id="all_episodes"><a name="all_episodes,"></a><pre><span class="Keyword">proc</span> <span class="Identifier">all_episodes</span><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span> <span class="Identifier">HttpRequestError</span><span class="Other">,</span>
<span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span>
<span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
return all information on all episodes.
</dd>
<dt id="get_season"><a name="get_season,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_season</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<dt id="get_season"><a name="get_season,int"></a><pre><span class="Keyword">proc</span> <span class="Identifier">get_season</span><span class="Other">(</span><span class="Identifier">season</span><span class="Other">:</span> <span class="Identifier">int</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
return all information on a single season.
</dd>
<dt id="search"><a name="search,string"></a><pre><span class="Keyword">proc</span> <span class="Identifier">search</span><span class="Other">(</span><span class="Identifier">term</span><span class="Other">:</span> <span class="Identifier">string</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<dt id="search"><a name="search,string"></a><pre><span class="Keyword">proc</span> <span class="Identifier">search</span><span class="Other">(</span><span class="Identifier">term</span><span class="Other">:</span> <span class="Identifier">string</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">seq</span><span class="Other">[</span><span class="Identifier">Episode</span><span class="Other">]</span> <span class="Other">{.</span><span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ValueError</span><span class="Other">,</span> <span class="Identifier">OSError</span><span class="Other">,</span>
<span class="Identifier">HttpRequestError</span><span class="Other">,</span> <span class="Identifier">OverflowError</span><span class="Other">,</span> <span class="Identifier">TimeoutError</span><span class="Other">,</span> <span class="Identifier">ProtocolError</span><span class="Other">,</span> <span class="Identifier">Exception</span><span class="Other">,</span>
<span class="Identifier">JsonParsingError</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">ReadIOEffect</span><span class="Other">,</span> <span class="Identifier">WriteIOEffect</span><span class="Other">,</span> <span class="Identifier">TimeEffect</span><span class="Other">]</span><span class="Other">.}</span></pre></dt>
<dd>
searches for episodes by the given query term.
@ -1270,7 +1284,7 @@ searches for episodes by the given query term.
<div class="twelve-columns footer">
<span class="nim-sprite"></span>
<br>
<small>Made with Nim. Generated: 2015-08-12 17:29:43 UTC</small>
<small>Made with Nim. Generated: 2015-08-17 15:01:16 UTC</small>
</div>
</div>
</div>

View File

@ -1,8 +0,0 @@
Flask==0.10.1
Jinja2==2.8
MarkupSafe==0.23
Werkzeug==0.10.4
argparse==1.2.1
gunicorn==19.3.0
itsdangerous==0.24
wsgiref==0.1.2

7
run/Caddyfile Normal file
View File

@ -0,0 +1,7 @@
ponyapi.apps.xeserv.us {
log syslog
proxy / http://127.0.0.1:6452 {
proxy_header Host {host}
}
}

13
run/ponyapi.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=PonyAPI Server listening on port 6452
Requires=docker.service
Restart=always
[Service]
ExecStartPre=-/usr/bin/docker pull xena/ponyapi
ExecStartPre=-/usr/bin/docker rm -f ponyapi
ExecStart=/usr/bin/docker run -p 6452:5000 --name ponyapi xena/ponyapi
ExecStop=/usr/bin/docker rm -f ponyapi
[Install]
WantedBy=network.target

25
stats.nim Normal file
View File

@ -0,0 +1,25 @@
import json
type
Bucket* = object of RootObj
## A collection of stats
name*: string
fails*: int
success*: int
proc `%`*(b: Bucket): JsonNode =
%*
{
"name": b.name,
"fails": b.fails,
"success": b.success,
}
var
all* = Bucket(name: "all")
newest* = Bucket(name: "newest")
random* = Bucket(name: "random")
lastAired* = Bucket(name: "lastAired")
seasonLookup* = Bucket(name: "seasonLookup")
episodeLookup* = Bucket(name: "episodeLookup")
search* = Bucket(name: "search")