Commits: 5

Introduce SQLite to store jokes

For now only /jokes endpoint is implemented. The others are commented out.

index 18b86e4..a818d1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+jokes.db
=.devenv
=.direnv
=__pycache__
index 75792ae..2f6297e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -27,7 +27,9 @@
=              modules = [
=                {
=                  # https://devenv.sh/reference/options/
-                  packages = [];
+                  packages = [
+                    pkgs.litecli
+                  ];
=
=                  languages.python = {
=                    enable = true;
new file mode 100644
index 0000000..83fd1e1
--- /dev/null
+++ b/init.sql
@@ -0,0 +1,26 @@
+Create table if not exists "joker" (
+  id integer primary key autoincrement,
+  name text not null unique,
+  password text not null
+);
+
+
+Create table if not exists "joke" (
+  id integer primary key autoincrement,
+  text text not null,
+  author integer not null references "joker.id"
+);
+
+
+Insert into joker ("name", "password") values
+("Quipster",            "1234"),
+("Giggle Guru",         "1234"),
+("Wit‑Wizard",          "1234"),
+("Chuckle Champion",    "1234");
+
+
+Insert into joke ("author", "text") values
+(1, "Why did the scarecrow get promoted? He was outstanding in his field."),
+(2, "I used to be a baker, but I couldn’t make enough dough."),
+(2, "What’s a snake’s favorite subject in school? Hiss‑tory."),
+(3, "What's big? Your nose.");
index be932e4..df65610 100644
--- a/main.py
+++ b/main.py
@@ -6,6 +6,8 @@ You can use it to teach concepts of REST.
=
=from fastapi import FastAPI
=from pydantic import BaseModel
+from typing import Optional
+import sqlite3
=import logging
=
=app = FastAPI()
@@ -18,30 +20,39 @@ def init():
=
=class Joke(BaseModel):
=    """A joke."""
-
+    id: Optional[int]
=    text: str
+    author: str
=
=
-jokes = [
-    Joke(text="How do you find a velociraptor?\n\n" +
-         "By taking the integral of the acceleraptor!")
-]
-
=
-@app.get("/")
-def get_random_joke() -> Joke:
-    """Get a single random joke."""
-    return jokes[0]
+# @app.get("/")
+# def get_random_joke() -> Joke:
+#     """Get a single random joke."""
+#     return jokes[0]
=
=
=@app.get("/jokes")
=def list_jokes() -> list[Joke]:
=    """List all jokes."""
-    return jokes
-
-
-@app.post("/jokes")
-def new_joke(joke: Joke) -> Joke:
-    """Write a new joke."""
-    jokes.append(joke)
-    return joke
+    with sqlite3.connect("jokes.db") as db:
+        db.row_factory = sqlite3.Row
+        cursor = db.cursor()
+        cursor.execute("""
+        Select
+            joke.id,
+            joke.text,
+            joker.name as author
+        from
+            joke
+            join joker on joke.author = joker.id
+        ;
+        """)
+        return [Joke(**row) for row in cursor.fetchall()]
+
+
+# @app.post("/jokes")
+# def new_joke(joke: Joke) -> Joke:
+#     """Write a new joke."""
+#     jokes.append(joke)
+#     return joke

Implement random joke endpoint

index df65610..365ecbf 100644
--- a/main.py
+++ b/main.py
@@ -26,10 +26,26 @@ class Joke(BaseModel):
=
=
=
-# @app.get("/")
-# def get_random_joke() -> Joke:
-#     """Get a single random joke."""
-#     return jokes[0]
+@app.get("/")
+def get_random_joke() -> Joke:
+    """Get a single random joke."""
+    with sqlite3.connect("jokes.db") as db:
+        db.row_factory = sqlite3.Row
+        cursor = db.cursor()
+        cursor.execute("""
+        Select
+            joke.id,
+            joke.text,
+            joker.name as author
+        from
+            joke
+            join joker on joke.author = joker.id
+        order by
+            random()
+        limit 1
+        ;
+        """)
+        return Joke(**cursor.fetchone())
=
=
=@app.get("/jokes")

Implement the POST /jokes/ endpoint

index 365ecbf..d697b50 100644
--- a/main.py
+++ b/main.py
@@ -4,7 +4,7 @@ A sample REST API service.
=You can use it to teach concepts of REST.
="""
=
-from fastapi import FastAPI
+from fastapi import FastAPI, HTTPException
=from pydantic import BaseModel
=from typing import Optional
=import sqlite3
@@ -67,8 +67,37 @@ def list_jokes() -> list[Joke]:
=        return [Joke(**row) for row in cursor.fetchall()]
=
=
-# @app.post("/jokes")
-# def new_joke(joke: Joke) -> Joke:
-#     """Write a new joke."""
-#     jokes.append(joke)
-#     return joke
+@app.post("/jokes", status_code=201)
+def new_joke(joke: Joke) -> Joke:
+    """Write a new joke."""
+    with sqlite3.connect("jokes.db") as db:
+        db.row_factory = sqlite3.Row
+        cursor = db.cursor()
+        author = cursor.execute("""
+        Select
+            id
+        from
+            joker
+        where
+            name = ?
+        ;
+        """, (joke.author,)).fetchone()
+
+        if author is None:
+            logger.debug(f"Specified author not found {joke.author}")
+            raise HTTPException(
+                status_code=422,
+                detail=f"The specified joker is not known: {joke.author}"
+            )
+
+        row = cursor.execute("""
+        Insert into joke (
+            text,
+            author
+        )
+        values (?, ?)
+        returning id, text
+        ;
+        """, (joke.text, author["id"])).fetchone()
+
+        return Joke(**row, author=joke.author)

Give the app a title and summary

index d697b50..d15e5f6 100644
--- a/main.py
+++ b/main.py
@@ -10,7 +10,11 @@ from typing import Optional
=import sqlite3
=import logging
=
-app = FastAPI()
+app = FastAPI(
+    title="JaaS",
+    summary="Joke as a service"
+)
+
=logger = logging.getLogger("uvicorn.error")
=
=@app.on_event("startup")

Implement the GET /jokes/

Also POST /jokes/ will only give a location of a new joke to allow for demonstration of response headers.

Also, remove the em-dash from initial authors set.

index 83fd1e1..3f5e347 100644
--- a/init.sql
+++ b/init.sql
@@ -15,7 +15,7 @@ Create table if not exists "joke" (
=Insert into joker ("name", "password") values
=("Quipster",            "1234"),
=("Giggle Guru",         "1234"),
-("Wit‑Wizard",          "1234"),
+("Wit-Wizard",          "1234"),
=("Chuckle Champion",    "1234");
=
=
index d15e5f6..58c1ec2 100644
--- a/main.py
+++ b/main.py
@@ -4,7 +4,7 @@ A sample REST API service.
=You can use it to teach concepts of REST.
="""
=
-from fastapi import FastAPI, HTTPException
+from fastapi import FastAPI, HTTPException, Response
=from pydantic import BaseModel
=from typing import Optional
=import sqlite3
@@ -71,6 +71,31 @@ def list_jokes() -> list[Joke]:
=        return [Joke(**row) for row in cursor.fetchall()]
=
=
+@app.get("/jokes/{id}")
+def get_joke(id: int) -> Joke:
+    """Get a single joke."""
+    with sqlite3.connect("jokes.db") as db:
+        db.row_factory = sqlite3.Row
+        cursor = db.cursor()
+        joke = cursor.execute("""
+        Select
+            joke.id,
+            joke.text,
+            joker.name as author
+        from
+            joke
+            join joker on joke.author = joker.id
+        where
+            joke.id = :id
+        ;
+        """, {"id": id}).fetchone()
+
+        if joke is None:
+            raise HTTPException(status_code=404)
+
+        return Joke(**joke)
+
+
=@app.post("/jokes", status_code=201)
=def new_joke(joke: Joke) -> Joke:
=    """Write a new joke."""
@@ -99,9 +124,14 @@ def new_joke(joke: Joke) -> Joke:
=            text,
=            author
=        )
-        values (?, ?)
-        returning id, text
+        values (:text, :author)
+        returning id
=        ;
-        """, (joke.text, author["id"])).fetchone()
+        """, {"text": joke.text, "author": author["id"]}).fetchone()
=
-        return Joke(**row, author=joke.author)
+        return Response(
+            headers={
+                "location": f"/jokes/{row["id"]}"
+            },
+            status_code=201
+        )