Skip to content

Commit a2534af

Browse files
authored
feat: add rest-api exercise (#41)
1 parent ce38147 commit a2534af

File tree

10 files changed

+345
-0
lines changed

10 files changed

+345
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,14 @@
225225
"practices": [],
226226
"prerequisites": [],
227227
"difficulty": 2
228+
},
229+
{
230+
"slug": "rest-api",
231+
"name": "REST API",
232+
"uuid": "51275817-1b95-48d9-b7ab-bbbf8720acb7",
233+
"practices": [],
234+
"prerequisites": [],
235+
"difficulty": 1
228236
}
229237
]
230238
},
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Instructions
2+
3+
Implement a RESTful API for tracking IOUs.
4+
5+
Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much.
6+
7+
Your task is to implement a simple [RESTful API][restful-wikipedia] that receives [IOU][iou]s as POST requests, and can deliver specified summary information via GET requests.
8+
9+
## API Specification
10+
11+
### User object
12+
13+
```json
14+
{
15+
"name": "Adam",
16+
"owes": {
17+
"Bob": 12.0,
18+
"Chuck": 4.0,
19+
"Dan": 9.5
20+
},
21+
"owed_by": {
22+
"Bob": 6.5,
23+
"Dan": 2.75
24+
},
25+
"balance": "<(total owed by other users) - (total owed to other users)>"
26+
}
27+
```
28+
29+
### Methods
30+
31+
| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload |
32+
| ------------------------ | ----------- | ------ | ------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- |
33+
| List of user information | GET | /users | `{"users":["Adam","Bob"]}` | `{"users":<List of all User objects>}` | `{"users":<List of User objects for <users> (sorted by name)}` |
34+
| Create user | POST | /add | `{"user":<name of new user (unique)>}` | N/A | `<User object for new user>` |
35+
| Create IOU | POST | /iou | `{"lender":<name of lender>,"borrower":<name of borrower>,"amount":5.25}` | N/A | `{"users":<updated User objects for <lender> and <borrower> (sorted by name)>}` |
36+
37+
## Other Resources
38+
39+
- [REST API Tutorial][restfulapi]
40+
- Example RESTful APIs
41+
- [GitHub][github-rest]
42+
- [Reddit][reddit-rest]
43+
44+
[restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer
45+
[iou]: https://en.wikipedia.org/wiki/IOU
46+
[github-rest]: https://developer.github.com/v3/
47+
[reddit-rest]: https://web.archive.org/web/20231202231149/https://www.reddit.com/dev/api/
48+
[restfulapi]: https://restfulapi.net/
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"authors": [
3+
"steffan153",
4+
"vaeng"
5+
],
6+
"files": {
7+
"solution": [
8+
"rest-api.sql"
9+
],
10+
"test": [
11+
"rest-api_test.sql"
12+
],
13+
"example": [
14+
".meta/example.sql"
15+
]
16+
},
17+
"blurb": "Implement a RESTful API for tracking IOUs."
18+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
UPDATE 'rest-api' AS CURRENT
2+
SET result = (
3+
SELECT json_object('users', IIF(json_type(DB.value) IS NULL, json_array(), json_array(DB.value)))
4+
FROM 'rest-api' AS RA
5+
LEFT JOIN json_each(RA.payload, '$.users') AS PL ON RA.payload = PL.json
6+
LEFT JOIN json_each(RA.database, '$.users') AS DB ON RA.database = DB.json AND json_extract(DB.value, '$.name') = PL.value
7+
WHERE (RA.database, RA.payload) = (CURRENT.database, CURRENT.payload)
8+
)
9+
WHERE url = '/users';
10+
11+
12+
UPDATE 'rest-api' AS CURRENT
13+
SET result = json_object('name', json_extract(payload, '$.user'), 'owes', json_object(), 'owed_by', json_object(), 'balance', 0)
14+
WHERE url = '/add';
15+
16+
17+
UPDATE 'rest-api' AS CURRENT
18+
SET result = json_array(
19+
-- update the lender:
20+
(
21+
SELECT json_object(
22+
'name', json_extract(payload, '$.lender'),
23+
'owes', json_patch(
24+
json_extract(value, '$.owes'),
25+
json_object(
26+
json_extract(payload, '$.borrower'),
27+
IIF(relative_balance < 0, -relative_balance, NULL)
28+
)
29+
),
30+
'owed_by', json_patch(
31+
json_extract(value, '$.owed_by'),
32+
json_object(
33+
json_extract(payload, '$.borrower'),
34+
IIF(relative_balance > 0, relative_balance, NULL)
35+
)
36+
),
37+
'balance', json_extract(value, '$.balance') + json_extract(payload, '$.amount')
38+
)
39+
FROM (
40+
SELECT
41+
*,
42+
IFNULL(json_extract(json_extract(value, '$.owed_by'), '$.' || json_extract(payload, '$.borrower')), 0) -
43+
IFNULL(json_extract(json_extract(value, '$.owes'), '$.' || json_extract(payload, '$.borrower')), 0) +
44+
json_extract(payload, '$.amount') AS relative_balance
45+
FROM json_each(database, '$.users')
46+
WHERE json = database AND json_extract(value, '$.name') == json_extract(payload, '$.lender')
47+
)
48+
),
49+
-- update the borrower:
50+
(
51+
SELECT json_object(
52+
'name', json_extract(payload, '$.borrower'),
53+
'owes', json_patch(
54+
json_extract(value, '$.owes'),
55+
json_object(
56+
json_extract(payload, '$.lender'),
57+
IIF(relative_balance < 0, -relative_balance, NULL)
58+
)
59+
),
60+
'owed_by', json_patch(
61+
json_extract(value, '$.owed_by'),
62+
json_object(
63+
json_extract(payload, '$.lender'),
64+
IIF(relative_balance > 0, relative_balance, NULL)
65+
)
66+
),
67+
'balance', json_extract(value, '$.balance') - json_extract(payload, '$.amount')
68+
)
69+
FROM (
70+
SELECT
71+
*,
72+
IFNULL(json_extract(json_extract(value, '$.owed_by'), '$.' || json_extract(payload, '$.lender')), 0) -
73+
IFNULL(json_extract(json_extract(value, '$.owes'), '$.' || json_extract(payload, '$.lender')), 0) -
74+
json_extract(payload, '$.amount') AS relative_balance
75+
FROM json_each(database, '$.users')
76+
WHERE json = database AND json_extract(value, '$.name') == json_extract(payload, '$.borrower')
77+
)
78+
)
79+
)
80+
WHERE url = '/iou';
81+
82+
-- order the result
83+
UPDATE 'rest-api' AS CURRENT
84+
SET result = (
85+
SELECT json_object('users', json_group_array(json(value)))
86+
FROM (
87+
SELECT value
88+
FROM json_each(result)
89+
ORDER BY value
90+
)
91+
)
92+
WHERE url = '/iou';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[5be01ffb-a814-47a8-a19f-490a5622ba07]
13+
description = "user management -> no users"
14+
15+
[382b70cc-9f6c-486d-9bee-fda2df81c803]
16+
description = "user management -> add user"
17+
18+
[d624e5e5-1abb-4f18-95b3-45d55c818dc3]
19+
description = "user management -> get single user"
20+
21+
[7a81b82c-7276-433e-8fce-29ce983a7c56]
22+
description = "iou -> both users have 0 balance"
23+
24+
[1c61f957-cf8c-48ba-9e77-b221ab068803]
25+
description = "iou -> borrower has negative balance"
26+
27+
[8a8567b3-c097-468a-9541-6bb17d5afc85]
28+
description = "iou -> lender has negative balance"
29+
30+
[29fb7c12-7099-4a85-a7c4-9c290d2dc01a]
31+
description = "iou -> lender owes borrower"
32+
33+
[ce969e70-163c-4135-a4a6-2c3a5da286f5]
34+
description = "iou -> lender owes borrower less than new loan"
35+
36+
[7f4aafd9-ae9b-4e15-a406-87a87bdf47a4]
37+
description = "iou -> lender owes borrower same as new loan"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DROP TABLE IF EXISTS "rest-api";
2+
CREATE TABLE "rest-api" (
3+
"database" TEXT,
4+
"payload" TEXT,
5+
"url" TEXT,
6+
"result" TEXT
7+
);
8+
9+
.mode csv
10+
.import ./data.csv "rest-api"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
DROP TABLE IF EXISTS tests;
2+
CREATE TABLE IF NOT EXISTS tests (
3+
-- uuid and name are taken from the test.toml file
4+
uuid TEXT PRIMARY KEY,
5+
name TEXT NOT NULL,
6+
-- The following section is needed by the online test-runner
7+
status TEXT DEFAULT 'fail',
8+
message TEXT,
9+
output TEXT,
10+
test_code TEXT,
11+
task_id INTEGER DEFAULT NULL,
12+
-- Here are columns for the actual tests
13+
database TEXT NOT NULL,
14+
payload TEXT NOT NULL,
15+
url TEXT INT NOT NULL,
16+
expected TEXT NOT NULL
17+
);
18+
19+
-- Note: the strings below _may_ contain literal tab, newline, or carriage returns.
20+
21+
INSERT INTO tests (uuid, name, database, payload, url, expected)
22+
VALUES
23+
("5be01ffb-a814-47a8-a19f-490a5622ba07",
24+
"no users",
25+
"{""users"":[]}",
26+
"{}",
27+
"/users",
28+
"{""users"":[]}"),
29+
30+
("382b70cc-9f6c-486d-9bee-fda2df81c803",
31+
"add user",
32+
"{""users"":[]}","{""user"":""Adam""}",
33+
"/add",
34+
"{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0}"),
35+
36+
("d624e5e5-1abb-4f18-95b3-45d55c818dc3",
37+
"get single user",
38+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}",
39+
"{""users"":[""Bob""]}",
40+
"/users",
41+
"{""users"":[{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}"),
42+
43+
("7a81b82c-7276-433e-8fce-29ce983a7c56",
44+
"both users have 0 balance",
45+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}",
46+
"{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}",
47+
"/iou",
48+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3},{""name"":""Bob"",""owes"":{""Adam"":3},""owed_by"":{},""balance"":-3}]}"),
49+
50+
("1c61f957-cf8c-48ba-9e77-b221ab068803",
51+
"borrower has negative balance",
52+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{""Chuck"":3},""owed_by"":{},""balance"":-3},{""name"":""Chuck"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3}]}",
53+
"{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}",
54+
"/iou",
55+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3},{""name"":""Bob"",""owes"":{""Adam"":3,""Chuck"":3},""owed_by"":{},""balance"":-6}]}"
56+
),
57+
58+
("8a8567b3-c097-468a-9541-6bb17d5afc85",
59+
"lender has negative balance",
60+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{""Chuck"":3},""owed_by"":{},""balance"":-3},{""name"":""Chuck"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3}]}",
61+
"{""lender"":""Bob"",""borrower"":""Adam"",""amount"":3}",
62+
"/iou",
63+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{""Chuck"":3},""owed_by"":{""Adam"":3},""balance"":0}]}"),
64+
65+
("29fb7c12-7099-4a85-a7c4-9c290d2dc01a",
66+
"lender owes borrower",
67+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}",
68+
"{""lender"":""Adam"",""borrower"":""Bob"",""amount"":2}",
69+
"/iou",
70+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":1},""owed_by"":{},""balance"":-1},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":1},""balance"":1}]}"),
71+
72+
("ce969e70-163c-4135-a4a6-2c3a5da286f5",
73+
"lender owes borrower less than new loan",
74+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}",
75+
"{""lender"":""Adam"",""borrower"":""Bob"",""amount"":4}",
76+
"/iou",
77+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{""Bob"":1},""balance"":1},{""name"":""Bob"",""owes"":{""Adam"":1},""owed_by"":{},""balance"":-1}]}"),
78+
79+
("7f4aafd9-ae9b-4e15-a406-87a87bdf47a4",
80+
"lender owes borrower same as new loan",
81+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}",
82+
"{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}",
83+
"/iou",
84+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}");

exercises/practice/rest-api/data.csv

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"{""users"":[]}","{}","/users",""
2+
"{""users"":[]}","{""user"":""Adam""}","/add",""
3+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}","{""users"":[""Bob""]}","/users",""
4+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{},""owed_by"":{},""balance"":0}]}","{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}","/iou",""
5+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{""Chuck"":3},""owed_by"":{},""balance"":-3},{""name"":""Chuck"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3}]}","{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}","/iou",""
6+
"{""users"":[{""name"":""Adam"",""owes"":{},""owed_by"":{},""balance"":0},{""name"":""Bob"",""owes"":{""Chuck"":3},""owed_by"":{},""balance"":-3},{""name"":""Chuck"",""owes"":{},""owed_by"":{""Bob"":3},""balance"":3}]}","{""lender"":""Bob"",""borrower"":""Adam"",""amount"":3}","/iou",""
7+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}","{""lender"":""Adam"",""borrower"":""Bob"",""amount"":2}","/iou",""
8+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}","{""lender"":""Adam"",""borrower"":""Bob"",""amount"":4}","/iou",""
9+
"{""users"":[{""name"":""Adam"",""owes"":{""Bob"":3},""owed_by"":{},""balance"":-3},{""name"":""Bob"",""owes"":{},""owed_by"":{""Adam"":3},""balance"":3}]}","{""lender"":""Adam"",""borrower"":""Bob"",""amount"":3}","/iou",""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Schema: CREATE TABLE "rest-api" ("database" TEXT, "payload" TEXT, "url" TEXT, "result" TEXT);
2+
-- Task: update the rest-api table and set the result based on the database, payload and url fields.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-- Create database:
2+
.read ./create_fixture.sql
3+
4+
-- Read user student solution and save any output as markdown in user_output.md:
5+
.mode markdown
6+
.output user_output.md
7+
.read ./rest-api.sql
8+
.output
9+
10+
-- Create a clean testing environment:
11+
.read ./create_test_table.sql
12+
13+
-- Comparison of user input and the tests updates the status for each test:
14+
UPDATE tests
15+
SET status = 'pass'
16+
FROM (SELECT database, url, payload, result FROM 'rest-api') AS actual
17+
WHERE (actual.database, actual.url, actual.payload) = (tests.database, tests.url, tests.payload)
18+
AND (SELECT COUNT(*) FROM json_tree(actual.result) as a, json_tree(tests.expected) as b WHERE (a.fullkey, a.atom) IS (b.fullkey, b.atom)) =
19+
(SELECT COUNT(*) FROM json_tree(tests.expected))
20+
AND (SELECT COUNT(*) FROM json_tree(tests.expected)) = (SELECT COUNT(*) FROM json_tree(actual.result));
21+
22+
-- Update message for failed tests to give helpful information:
23+
UPDATE tests
24+
SET message = 'Result for ' || tests.database || ' as ' || tests.payload || ' is ' || actual.result || ', but should be ' || tests.expected
25+
FROM (SELECT database, payload, url, result FROM 'rest-api') AS actual
26+
WHERE (actual.database, actual.payload, actual.url) = (tests.database, tests.payload, tests.url) AND tests.status = 'fail';
27+
28+
-- Save results to ./output.json (needed by the online test-runner)
29+
.mode json
30+
.once './output.json'
31+
SELECT name, status, message, output, test_code, task_id
32+
FROM tests;
33+
34+
-- Display test results in readable form for the student:
35+
.mode table
36+
SELECT name, status, message
37+
FROM tests;

0 commit comments

Comments
 (0)