Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3318551
Add /api/signup-post endpoint
krestenlaust Dec 26, 2024
14ab546
Make schemes more generic
krestenlaust Dec 26, 2024
b7df05b
Add /api/signup-post output
krestenlaust Dec 26, 2024
4112320
fixup! Add /api/signup-post output
krestenlaust Dec 28, 2024
36f7127
Simplify /api/signup-post endpoint
krestenlaust Dec 28, 2024
f807d90
Add implementation for /api/signup-post endpoint
krestenlaust Dec 28, 2024
bdb0592
Return only signup ID and due
krestenlaust Dec 28, 2024
649cb36
Rename local variable
krestenlaust Jan 11, 2025
44ed033
Move post_signup
krestenlaust Jan 11, 2025
6ceacf8
Add api/signup/status endpoint to spec
krestenlaust Jan 11, 2025
bbbb15a
Update /api/signup/status to take in username
krestenlaust Jan 13, 2025
bdc077f
Merge branch 'next' into feat/api-signup
krestenlaust Jan 13, 2025
da96b25
fixup! Merge branch 'next' into feat/api-signup
krestenlaust Jan 13, 2025
8d34ee7
Implement /api/signup/status
krestenlaust Jan 13, 2025
ff3905c
fixup! Update /api/signup/status to take in username
krestenlaust Jan 13, 2025
202fc8c
Increment API version to 1.1.1
krestenlaust Jan 13, 2025
bd0fc87
Merge branch 'next' into feat/api-signup
krestenlaust Jan 15, 2025
36a9a9a
Add CSRF exemption
krestenlaust Jan 15, 2025
5fc7abd
Update response to fit spec
krestenlaust Jan 15, 2025
cd9c700
Add tests to validate form on API call
krestenlaust Jan 15, 2025
1168655
fixup! Add tests to validate form on API call
krestenlaust Jan 15, 2025
67f204c
Handle duplicate username in perform_signup
krestenlaust Jan 15, 2025
7be4a15
Internally map 'education' parameter
krestenlaust Feb 12, 2025
89896e6
Merge branch 'next' into feat/api-signup
krestenlaust Oct 21, 2025
869c505
Merge branch 'next' into feat/api-signup
krestenlaust Jan 7, 2026
a2892d7
Reorder API endpoints to improve test results
krestenlaust Jan 7, 2026
d0246b5
Refactor variable
krestenlaust Jan 7, 2026
c00ed9b
Inverse username-replacement in cases of signup api
krestenlaust Jan 7, 2026
612e808
fixup! Inverse username-replacement in cases of signup api
krestenlaust Jan 7, 2026
64e0651
fixup! fixup! Inverse username-replacement in cases of signup api
krestenlaust Jan 7, 2026
3d02219
More intricite logic for handling signup case
krestenlaust Jan 7, 2026
d828d68
Even more complex logic
krestenlaust Jan 7, 2026
5a0906d
Adjust API message
krestenlaust Jan 7, 2026
62a42a6
Update content type
krestenlaust Jan 7, 2026
3177b89
Test disable
krestenlaust Jan 7, 2026
904b58c
Correct request body provided in test
krestenlaust Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions openapi/dredd_hook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import dredd_hooks as hooks
import json
from utils import update_query_parameter_values, update_dictionary_values
from utils import update_query_parameter_values, update_dictionary_values, replace_json_dictionary_values

not_found_parameter_values = {
'room_id': 1,
Expand All @@ -26,17 +26,20 @@ def replace_4xx_parameter_values(transaction):
It isn't possible to specify individual parameter example values for each response type in OpenAPI.
To properly test the return value of not-found parameters, replace all parameters.
"""
if transaction['expected']['statusCode'][0] == '4':
new_path = update_query_parameter_values(transaction['fullPath'], not_found_parameter_values)
print(f"Update endpoint path, from '{transaction['fullPath']}' to '{new_path}'")
transaction['fullPath'] = new_path
transaction['request']['uri'] = new_path


@hooks.before_each
def replace_body_in_post_requests(transaction):
if transaction['expected']['statusCode'][0] == '4' and transaction['id'].startswith("POST"):
body = json.loads(transaction['request']['body'])
update_dictionary_values(body, not_found_parameter_values)

transaction['request']['body'] = json.dumps(body)
replace_username = transaction['expected']['statusCode'][0] == '4'

if transaction['id'].startswith("POST"):
# Signup is opposite, since we want to sign up a user that doesn't already exist.
if transaction['id'].endswith("/api/signup"):
replace_username = not replace_username

if replace_username:
transaction['request']['body'] = replace_json_dictionary_values(
transaction['request']['body'],
not_found_parameter_values)
else:
if replace_username:
new_path = update_query_parameter_values(transaction['fullPath'], not_found_parameter_values)
print(f"Update endpoint path, from '{transaction['fullPath']}' to '{new_path}'")
transaction['fullPath'] = new_path
transaction['request']['uri'] = new_path
156 changes: 155 additions & 1 deletion openapi/stregsystem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ info:
Existing client software utilizing the API include Stregsystem-CLI (STS) and Fappen (F-Club Web App).

Disclaimer - The implementation is not generated using this specification, therefore they can get out of sync if changes are made directly to the codebase without updating the OpenAPI specification file accordingly.
version: "1.1"
version: "1.1.1"
externalDocs:
description: Find out more about Stregsystemet at GitHub.
url: https://github.com/f-klubben/stregsystemet/
Expand All @@ -21,6 +21,8 @@ tags:
description: Related to the products.
- name: Sale
description: Related to performing a sale.
- name: Signup
description: Related to registration of new members.
paths:
/api/member:
get:
Expand Down Expand Up @@ -159,6 +161,38 @@ paths:
$ref: '#/components/responses/SaleSuccess'
'400':
$ref: '#/components/responses/Member_RoomIdParameter_BadResponse'
/api/signup/status:
get:
tags:
- Signup
summary: Gets status regarding a signup
description: Retrieves the signup status for a specific member by username.
operationId: api_signup_status
parameters:
- $ref: '#/components/parameters/username_param'
responses:
'200':
$ref: '#/components/responses/SignupStatus'
'400':
$ref: '#/components/responses/MemberUsernameParameter_BadResponse'
/api/signup:
post:
tags:
- Signup
summary: Posts a signup
description: Performs a signup using member info.
operationId: api_signup
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/signup_input'
responses:
'200':
$ref: '#/components/responses/SignupSuccess'
'400':
$ref: '#/components/responses/Signup_BadResponse'
components:
examples:
MemberNotFoundExample:
Expand All @@ -182,7 +216,20 @@ components:
MissingMemberUsernameExample:
summary: No username given
value: "Parameter missing: username"
UsernameTakenExample:
summary: Member with that username already exist
value: "Username already taken"
MissingOrInvalidParameterExample:
summary: A parameter is invalid or missing
value: "Parameter invalid: <parameter>"
parameters:
signup_id_param:
name: signup_id
in: query
description: Signup ID of the signup to retrieve.
required: true
schema:
$ref: '#/components/schemas/signup_id'
member_id_param:
name: member_id
in: query
Expand Down Expand Up @@ -239,6 +286,12 @@ components:
missingMemberUsernameMessage:
type: string
example: "Parameter missing: username"
usernameTakenMessage:
type: string
example: "Username already taken"
invalidParameterMessage:
type: string
example: "Parameter invalid: <parameter>"
balance:
type: integer
example: 20000
Expand All @@ -257,6 +310,35 @@ components:
room_id:
type: integer
example: 10
email:
type: string
format: email
example: [email protected]
firstname:
type: string
example: Kresten
lastname:
type: string
example: Laust
education:
type: string
description: Acknowledged shortening, e.g. sw/ixd/dad/dat
example: sw
gender:
type: string
enum:
- U
- M
- F
example: M
approval_status:
type: string
description: U = Unreviewed, A = Approved, I = Ignored, R = Rejected.
enum:
- U
- A
- I
- R
timestamp:
type: string
format: date-time
Expand All @@ -274,13 +356,24 @@ components:
type: integer
example: 1800
stregoere_balance:
type: integer
example: 15000
stregoere_due:
type: integer
example: 20000
stregkroner_balance:
description: Stregbalance in kroner, only used in API-Sale
type: number
format: float
example: 182.00
signup_id:
type: integer
example: 5
named_products_example:
type: object
properties:
beer:
$ref: '#/components/schemas/product_id'
sale_input:
type: object
properties:
Expand All @@ -290,6 +383,21 @@ components:
$ref: '#/components/schemas/buystring'
room:
$ref: '#/components/schemas/room_id'
signup_input:
type: object
properties:
education:
$ref: '#/components/schemas/education'
username:
$ref: '#/components/schemas/username'
email:
$ref: '#/components/schemas/email'
firstname:
$ref: '#/components/schemas/firstname'
lastname:
$ref: '#/components/schemas/lastname'
gender:
$ref: '#/components/schemas/gender'
active_product:
type: object
properties:
Expand Down Expand Up @@ -359,6 +467,13 @@ components:
member_has_low_balance:
type: boolean
example: false
signup_values_result_example:
type: object
properties:
due:
$ref: '#/components/schemas/stregoere_due'
username:
$ref: '#/components/schemas/username'
sale_values_result_example:
type: object
properties:
Expand Down Expand Up @@ -472,6 +587,17 @@ components:
properties:
sales:
$ref: '#/components/schemas/sales'
SignupStatus:
description: Signup information found.
content:
application/json:
schema:
type: object
properties:
due:
$ref: '#/components/schemas/stregoere_due'
status:
$ref: '#/components/schemas/approval_status'
NamedProducts:
description: Dictionary of all named_product names.
content:
Expand Down Expand Up @@ -511,6 +637,21 @@ components:
example: "OK"
values:
$ref: '#/components/schemas/sale_values_result_example'
SignupSuccess:
description: An object containing info regarding the signup.
content:
application/json:
schema:
type: object
properties:
status:
type: integer
example: 200
msg:
type: string
example: "OK"
values:
$ref: '#/components/schemas/signup_values_result_example'
QRCodeGenerated:
description: QR code with link to open MobilePay with the provided information.
content:
Expand Down Expand Up @@ -594,3 +735,16 @@ components:
$ref: '#/components/examples/InvalidRoomIdExample'
missingRoomId:
$ref: '#/components/examples/MissingRoomIdExample'
Signup_BadResponse:
description: Username is taken, missing parameter, or invalid parameter.
content:
text/html; charset=utf-8:
schema:
oneOf:
- $ref: '#/components/schemas/usernameTakenMessage'
- $ref: '#/components/schemas/invalidParameterMessage'
examples:
usernameTaken:
$ref: '#/components/examples/UsernameTakenExample'
invalidParameter:
$ref: '#/components/examples/MissingOrInvalidParameterExample'
27 changes: 27 additions & 0 deletions openapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
from typing import Any
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
import json


def replace_json_dictionary_values(body, replacements):
"""
body: JSON string or dict
replacements: dict of key -> replacement value
Generated by ChatGPT
"""
if isinstance(body, str):
data = json.loads(body)
else:
data = body

def recurse(obj):
if isinstance(obj, dict):
for k, v in obj.items():
if k in replacements:
obj[k] = replacements[k]
else:
recurse(v)
elif isinstance(obj, list):
for item in obj:
recurse(item)

recurse(data)

return json.dumps(data)

def update_dictionary_values(original: dict[Any, Any], replacement: dict[Any, Any]) -> None:
"""
Same as dict.update(...) but doesn't add new keys.
Expand Down
Loading