-
Notifications
You must be signed in to change notification settings - Fork 2
Milestone 2 Report
We designed this practice app to learn working as a group on github for future releases.
Aras Güngöre |
Begüm Arslan |
Cahid Enes Keleş |
Egecan Serbester |
Halil İbrahim Gürbüz |
|
Hasan Bingölbali |
Mehmet Emin İpekdal |
Mehmet Kuzulugil |
Merve Gürbüz | Ömer Şahin Albayram | Ramazan Burak Sarıtaş |
We used FastAPI for the backend and next.js for the front end for our practice app. The API's are picked considering future use. We succesfully deployed our docker on AWS.
- URL of practice app code and the tag https://practice-app.online/notifications : Appended late by instructor's permission due to https.
- Any information we may need to test your application Check the README for server and client
- Instructions for building the application with docker. – API URL (the URL should take us to a documented API) https://practice-app.online/api/docs
- Links to meeting notes related to practice app
- Lessons learned: evaluation of tools and processes utilized to manage the team project.
- Visual Studio Code
- Github
- FastAPI The easiness with documenting API's were notable.
- MongoDB and PostgreSQL This was a challenge we faced as a group. We decided on using both after careful consideration to test them for our purposes. ``
-
- TimeZoneDB API
- endpoint: http://api.timezonedb.com/v2.1/get-time-zone?key=API_KEY
- POST request
- Description: Get local time of a city by its name, time zone, latitude & longtiude, or IP address.
- Parameters:
- key: Your unique API key you get after register your account.
- by: The method of lookup
- zone: A time zone abbreviation or time zone name. Required if lookup by zone method.
- lat: Latitude of a city. Required if lookup by position method.
- lng: Longitude of a city. Required if lookup by position method.
- city: The name of a city. Use asterisk (*) for wildcard search. Required if lookup by city method.
- Parameters:
- Description: Get local time of a city by its name, time zone, latitude & longtiude, or IP address.
-
- get_timezone
- Route: /timezone/get_timezone
- Description: Convert latitude and longitude data to timezone and date.
- list_timezones
- Route: /tz_conversion/list_timezones
- Description: Retrieve a list of all available timezones from the Timezone DB API.
- convert_time
- Route: /tz_conversion/convert_time
- Description: Convert a given time from one timezone to another.
- saved_conversions
- Route: /tz_conversion/saved_conversions
- Description: Retrieve a list of saved time conversions from the MongoDB database.
- delete_conversion
- Route: /tz_conversion/delete_conversion
- Delete a saved time conversion from the MongoDB database.
- get_timezone
I have written 5 unit tests, one for each API function.
class TimezoneTests(unittest.TestCase):
def test_get_timezone(self):
r = requests.post(f'http://0.0.0.0:8000{api_prefix}/timezone/get_timezone', json={
"latitude": "122",
"longitude": "232",
})
self.assertEqual(r.status_code, 200)
@pytest.mark.parametrize(
"expected_status_code",
[(200)],
)
def test_list_timezones(expected_status_code):
client = TestClient(app)
response = client.get("/tz_conversion/list_timezones")
assert response.status_code == expected_status_code
@pytest.mark.parametrize(
"time, from_tz, to_tz, expected_status_code",
[
("2023-05-12 15:00:00", "America/New_York", "Europe/London", 200),
("2023-05-12 15:00:00", "Invalid timezone", "Europe/London", 400),
("2023-05-12 15:00:00", "America/New_York", "Invalid timezone", 400),
("2023-05-12T15:00:00", "America/New_York", "Europe/London", 400),
],
)
def test_convert_time(time, from_tz, to_tz, expected_status_code):
client = TestClient(app)
response = client.post(
"/tz_conversion/convert_time", json={"time": time, "from_tz": from_tz, "to_tz": to_tz}
)
assert response.status_code == expected_status_code
@pytest.mark.parametrize(
"expected_status_code",
[(200)],
)
def test_get_saved_conversions(expected_status_code):
client = TestClient(app)
response = client.get("/tz_conversion/saved_conversions")
assert response.status_code == expected_status_code
@pytest.mark.parametrize(
"conversion_name, expected_status_code",
[
("Invalid name", 404),
],
)
def test_delete_conversion(conversion_name, expected_status_code):
client = TestClient(app)
response = client.post("/tz_conversion/delete_conversion",
json={"conversion_name": conversion_name})
assert response.status_code == expected_status_code
import requests
uri = 'http://127.0.0.1:8000'
# POST Request Example
response = requests.post(uri + '/timezone/get_timezone', json={
"latitude": 120.1,
"longitude": 23.4
})
print('Headers:', response.headers)
print('Status Code:', response.status_code)
print('Json:', response.json())
Headers: {'date': 'Fri, 12 May 2023 20:37:25 GMT', 'server': 'uvicorn', 'content-length': '61', 'content-type': 'application/json'}
Status Code: 200
Json: {'timezone': 'America/Anchorage', 'date': '2023-05-12 12:37:23'}
# GET Request Example
response = requests.get(uri + '/tz_conversion/list_timezones')
print('Headers:', response.headers)
print('Status Code:', response.status_code)
print('Json:', response.json())
Headers: {'date': 'Fri, 12 May 2023 20:37:25 GMT', 'server': 'uvicorn', 'content-length': '7722', 'content-type': 'application/json'}
Status Code: 200
Json: ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara', 'Africa/Bamako', 'Africa/Bangui', 'Africa/Banjul', 'Africa/Bissau', 'Africa/Blantyre', 'Africa/Brazzaville', 'Africa/Bujumbura', 'Africa/Cairo', 'Africa/Casablanca', 'Africa/Ceuta', 'Africa/Conakry', 'Africa/Dakar', 'Africa/Dar_es_Salaam', 'Africa/Djibouti', 'Africa/Douala', 'Africa/El_Aaiun', 'Africa/Freetown', 'Africa/Gaborone', 'Africa/Harare', 'Africa/Johannesburg', 'Africa/Juba', 'Africa/Kampala', 'Africa/Khartoum', 'Africa/Kigali', 'Africa/Kinshasa', 'Africa/Lagos', 'Africa/Libreville', 'Africa/Lome', 'Africa/Luanda', 'Africa/Lubumbashi', 'Africa/Lusaka', 'Africa/Malabo', 'Africa/Maputo', 'Africa/Maseru', 'Africa/Mbabane', 'Africa/Mogadishu', 'Africa/Monrovia', 'Africa/Nairobi', 'Africa/Ndjamena', 'Africa/Niamey', 'Africa/Nouakchott', 'Africa/Ouagadougou', 'Africa/Porto-Novo', 'Africa/Sao_Tome', 'Africa/Tripoli', 'Africa/Tunis', 'Africa/Windhoek', 'America/Adak', 'America/Anchorage', 'America/Anguilla', 'America/Antigua', 'America/Araguaina', 'America/Argentina/Buenos_Aires', 'America/Argentina/Catamarca', 'America/Argentina/Cordoba', 'America/Argentina/Jujuy', 'America/Argentina/La_Rioja', 'America/Argentina/Mendoza', 'America/Argentina/Rio_Gallegos', 'America/Argentina/Salta', 'America/Argentina/San_Juan', 'America/Argentina/San_Luis', 'America/Argentina/Tucuman', 'America/Argentina/Ushuaia', 'America/Aruba', 'America/Asuncion', 'America/Atikokan', 'America/Bahia', 'America/Bahia_Banderas', 'America/Barbados', 'America/Belem', 'America/Belize', 'America/Blanc-Sablon', 'America/Boa_Vista', 'America/Bogota', 'America/Boise', 'America/Cambridge_Bay', 'America/Campo_Grande', 'America/Cancun', 'America/Caracas', 'America/Cayenne', 'America/Cayman', 'America/Chicago', 'America/Chihuahua', 'America/Ciudad_Juarez', 'America/Costa_Rica', 'America/Creston', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', 'America/Dawson', 'America/Dawson_Creek', 'America/Denver', 'America/Detroit', 'America/Dominica', 'America/Edmonton', 'America/Eirunepe', 'America/El_Salvador', 'America/Fort_Nelson', 'America/Fortaleza', 'America/Glace_Bay', 'America/Goose_Bay', 'America/Grand_Turk', 'America/Grenada', 'America/Guadeloupe', 'America/Guatemala', 'America/Guayaquil', 'America/Guyana', 'America/Halifax', 'America/Havana', 'America/Hermosillo', 'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo', 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', 'America/Indiana/Vevay', 'America/Indiana/Vincennes', 'America/Indiana/Winamac', 'America/Inuvik', 'America/Iqaluit', 'America/Jamaica', 'America/Juneau', 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', 'America/Kralendijk', 'America/La_Paz', 'America/Lima', 'America/Los_Angeles', 'America/Lower_Princes', 'America/Maceio', 'America/Managua', 'America/Manaus', 'America/Marigot', 'America/Martinique', 'America/Matamoros', 'America/Mazatlan', 'America/Menominee', 'America/Merida', 'America/Metlakatla', 'America/Mexico_City', 'America/Miquelon', 'America/Moncton', 'America/Monterrey', 'America/Montevideo', 'America/Montserrat', 'America/Nassau', 'America/New_York', 'America/Nome', 'America/Noronha', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Nuuk', 'America/Ojinaga', 'America/Panama', 'America/Paramaribo', 'America/Phoenix', 'America/Port-au-Prince', 'America/Port_of_Spain', 'America/Porto_Velho', 'America/Puerto_Rico', 'America/Punta_Arenas', 'America/Rankin_Inlet', 'America/Recife', 'America/Regina', 'America/Resolute', 'America/Rio_Branco', 'America/Santarem', 'America/Santiago', 'America/Santo_Domingo', 'America/Sao_Paulo', 'America/Scoresbysund', 'America/Sitka', 'America/St_Barthelemy', 'America/St_Johns', 'America/St_Kitts', 'America/St_Lucia', 'America/St_Thomas', 'America/St_Vincent', 'America/Swift_Current', 'America/Tegucigalpa', 'America/Thule', 'America/Tijuana', 'America/Toronto', 'America/Tortola', 'America/Vancouver', 'America/Whitehorse', 'America/Winnipeg', 'America/Yakutat', 'Antarctica/Casey', 'Antarctica/Davis', 'Antarctica/DumontDUrville', 'Antarctica/Macquarie', 'Antarctica/Mawson', 'Antarctica/McMurdo', 'Antarctica/Palmer', 'Antarctica/Rothera', 'Antarctica/Syowa', 'Antarctica/Troll', 'Antarctica/Vostok', 'Arctic/Longyearbyen', 'Asia/Aden', 'Asia/Almaty', 'Asia/Amman', 'Asia/Anadyr', 'Asia/Aqtau', 'Asia/Aqtobe', 'Asia/Ashgabat', 'Asia/Atyrau', 'Asia/Baghdad', 'Asia/Bahrain', 'Asia/Baku', 'Asia/Bangkok', 'Asia/Barnaul', 'Asia/Beirut', 'Asia/Bishkek', 'Asia/Brunei', 'Asia/Chita', 'Asia/Choibalsan', 'Asia/Colombo', 'Asia/Damascus', 'Asia/Dhaka', 'Asia/Dili', 'Asia/Dubai', 'Asia/Dushanbe', 'Asia/Famagusta', 'Asia/Gaza', 'Asia/Hebron', 'Asia/Ho_Chi_Minh', 'Asia/Hong_Kong', 'Asia/Hovd', 'Asia/Irkutsk', 'Asia/Jakarta', 'Asia/Jayapura', 'Asia/Jerusalem', 'Asia/Kabul', 'Asia/Kamchatka', 'Asia/Karachi', 'Asia/Kathmandu', 'Asia/Khandyga', 'Asia/Kolkata', 'Asia/Krasnoyarsk', 'Asia/Kuala_Lumpur', 'Asia/Kuching', 'Asia/Kuwait', 'Asia/Macau', 'Asia/Magadan', 'Asia/Makassar', 'Asia/Manila', 'Asia/Muscat', 'Asia/Nicosia', 'Asia/Novokuznetsk', 'Asia/Novosibirsk', 'Asia/Omsk', 'Asia/Oral', 'Asia/Phnom_Penh', 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qatar', 'Asia/Qostanay', 'Asia/Qyzylorda', 'Asia/Riyadh', 'Asia/Sakhalin', 'Asia/Samarkand', 'Asia/Seoul', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Srednekolymsk', 'Asia/Taipei', 'Asia/Tashkent', 'Asia/Tbilisi', 'Asia/Tehran', 'Asia/Thimphu', 'Asia/Tokyo', 'Asia/Tomsk', 'Asia/Ulaanbaatar', 'Asia/Urumqi', 'Asia/Ust-Nera', 'Asia/Vientiane', 'Asia/Vladivostok', 'Asia/Yakutsk', 'Asia/Yangon', 'Asia/Yekaterinburg', 'Asia/Yerevan', 'Atlantic/Azores', 'Atlantic/Bermuda', 'Atlantic/Canary', 'Atlantic/Cape_Verde', 'Atlantic/Faroe', 'Atlantic/Madeira', 'Atlantic/Reykjavik', 'Atlantic/South_Georgia', 'Atlantic/St_Helena', 'Atlantic/Stanley', 'Australia/Adelaide', 'Australia/Brisbane', 'Australia/Broken_Hill', 'Australia/Darwin', 'Australia/Eucla', 'Australia/Hobart', 'Australia/Lindeman', 'Australia/Lord_Howe', 'Australia/Melbourne', 'Australia/Perth', 'Australia/Sydney', 'Europe/Amsterdam', 'Europe/Andorra', 'Europe/Astrakhan', 'Europe/Athens', 'Europe/Belgrade', 'Europe/Berlin', 'Europe/Bratislava', 'Europe/Brussels', 'Europe/Bucharest', 'Europe/Budapest', 'Europe/Busingen', 'Europe/Chisinau', 'Europe/Copenhagen', 'Europe/Dublin', 'Europe/Gibraltar', 'Europe/Guernsey', 'Europe/Helsinki', 'Europe/Isle_of_Man', 'Europe/Istanbul', 'Europe/Jersey', 'Europe/Kaliningrad', 'Europe/Kirov', 'Europe/Kyiv', 'Europe/Lisbon', 'Europe/Ljubljana', 'Europe/London', 'Europe/Luxembourg', 'Europe/Madrid', 'Europe/Malta', 'Europe/Mariehamn', 'Europe/Minsk', 'Europe/Monaco', 'Europe/Moscow', 'Europe/Oslo', 'Europe/Paris', 'Europe/Podgorica', 'Europe/Prague', 'Europe/Riga', 'Europe/Rome', 'Europe/Samara', 'Europe/San_Marino', 'Europe/Sarajevo', 'Europe/Saratov', 'Europe/Simferopol', 'Europe/Skopje', 'Europe/Sofia', 'Europe/Stockholm', 'Europe/Tallinn', 'Europe/Tirane', 'Europe/Ulyanovsk', 'Europe/Vaduz', 'Europe/Vatican', 'Europe/Vienna', 'Europe/Vilnius', 'Europe/Volgograd', 'Europe/Warsaw', 'Europe/Zagreb', 'Europe/Zurich', 'Indian/Antananarivo', 'Indian/Chagos', 'Indian/Christmas', 'Indian/Cocos', 'Indian/Comoro', 'Indian/Kerguelen', 'Indian/Mahe', 'Indian/Maldives', 'Indian/Mauritius', 'Indian/Mayotte', 'Indian/Reunion', 'Pacific/Apia', 'Pacific/Auckland', 'Pacific/Bougainville', 'Pacific/Chatham', 'Pacific/Chuuk', 'Pacific/Easter', 'Pacific/Efate', 'Pacific/Fakaofo', 'Pacific/Fiji', 'Pacific/Funafuti', 'Pacific/Galapagos', 'Pacific/Gambier', 'Pacific/Guadalcanal', 'Pacific/Guam', 'Pacific/Honolulu', 'Pacific/Kanton', 'Pacific/Kiritimati', 'Pacific/Kosrae', 'Pacific/Kwajalein', 'Pacific/Majuro', 'Pacific/Marquesas', 'Pacific/Midway', 'Pacific/Nauru', 'Pacific/Niue', 'Pacific/Norfolk', 'Pacific/Noumea', 'Pacific/Pago_Pago', 'Pacific/Palau', 'Pacific/Pitcairn', 'Pacific/Pohnpei', 'Pacific/Port_Moresby', 'Pacific/Rarotonga', 'Pacific/Saipan', 'Pacific/Tahiti', 'Pacific/Tarawa', 'Pacific/Tongatapu', 'Pacific/Wake', 'Pacific/Wallis']
I have developed a web app so when I enter a location in latitude and longitude, it displays the current time and time zone along with its respective location on the map.
-
- I initially wanted to use Google Maps Directions API for getting directions to a disaster area, however it was too difficult to implement so I switched to a more simple TimeZoneDB API.
- Difference in payload between client-side and server-side caused Unprocessable Entity problem which took me a while to find the source of it.
https://practice-app.online/notifications
-
Issues related to research and documentation: #164 #162 #160 #199 #195 related to practice App: #213 #197 #190 #187 #181 #171
-
- Instance ID API
- endpoint: https://iid.googleapis.com/iid/info/IID_TOKEN
- GET request
- Description:
- Authorization: key=YOUR_API_KEY. Set this parameter in the header. *[optional] boolean details: set this query parameter to true to get FCM or GCM topic subscription information (if any) associated with this token. When not specified, defaults to false. On success the call returns HTTP status 200 and a JSON object containing:
- application - package name associated with the token.
- authorizedEntity - projectId authorized to send to the token.
- applicationVersion - version of the application.
- appSigner - sha1 fingerprint for the signature applied to the package.
- Indicates which party signed the app; for example,Play Store.
- platform - returns ANDROID, IOS, or CHROME to indicate the device platform to which the token belongs. If the details flag is set:
- rel - relations associated with the token. For example, a list of topic subscriptions. I used the API for retriveving topics subscribed for client tokens to show it on notifications page.
- Description:
- endpoint: https://iid.googleapis.com/iid/v1/IID_TOKEN/rel/topics/TOPIC_NAME
- POST request
- Works similarly for subscribing a topic. I used the functions for the usage of
this API:
messaging.subscribe_to_topic(subscription.token, subscription.topic)
- Works similarly for subscribing a topic. I used the functions for the usage of
this API:
- Firebase Cloud Messaging API
- Endpoint https://fcm.googleapis.com/v1/{parent=projects/*}/messages:send
- POST request
- parent: It contains the Firebase project id
- data has the message with topic, title and body and headers include an authorization token.
- The api can be used for sending notifications to topic and a client token
-
- create_subscription
- Route: /notifications/subscriptions
- Description: This function uses firebase API to subscribe a client to a topic using client token. Also the subscription is added to database.
- unsubscription
- Route: /notifications/unsubscribe
- Description: This function uses firebase API to unsubscribe a client from a topic using client token. Also the subscription is added to database.
- get_subscriptions
- Route: /notifications/subscriptions
- Description: This function return a list of subscriptions from database.
- get_client_info
- Route: /notifications/subscriptions/{device_token}
- Description: This function return a list of subscriptions of a single client using Instance ID API using device token.
- send_notif
- Route: /notifications/send_notification_to_one
- Description: This function sends a single notification to a device token.
- create_event
- Route: /notifications/send_notification
- Description: This function sends notifications to devices that are subscribed to a topic.
- get_notif
- Route: /notifications/send_notification
- Description: This function gets notifications that were sent.
- create_subscription
I have written 8 tests for the functions that checks if the calls are successfull:
-
- Request:
axios.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/notifications/send_notification`, JSON.stringify(notificationData), { headers: { 'content-type': 'application/json' } });
- Response:
HTTP/1.1 200 OK content-type: application/json [ {notification: 'Driver Need in Istanbul', message: 'Notification is sended'} ]
- Request:
axios.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/notifications/subscriptions`, JSON.stringify(subscriptionData), { headers: { 'content-type': 'application/json' } });
- Response:
HTTP/1.1 200 OK content-type: application/json [ {success_count: 1, message: 'Tokens were subscribed successfully'} ]
- Request:
axios.post(`${process.env.NEXT_PUBLIC_BACKEND_URL}/notifications/unsubscribe`, JSON.stringify(subscriptionData), { headers: { 'content-type': 'application/json' } });
- Response:
HTTP/1.1 200 OK content-type: application/json [ {success_count: 1, message: 'Tokens were unsubscribed successfully'} ]
- Request:
axios.get(`${process.env.NEXT_PUBLIC_BACKEND_URL}/notifications/subscriptions/${token}`, { headers: { 'content-type': 'application/json' } });
- Response:
HTTP/1.1 200 OK content-type: application/json ['Driver', 'Food']
- Request:
I have had a hard time with Firebase Cloud Messaging API configuration. I had to find a way to get Bearer token that expires in a short time. I used many secret keys and tokens, which caused some trouble to my group members:) Also I never had an experience with APIs, FastAPI and next.js. For all of those things I got many help from Merve Gürbüz. Also Firebase needs HTTPS connection for sending notification and others, I used ngrok for test purposes. I also had a hard time with git, but I feel more confident at resolving merge conflicts now. The most stressfull part was about database choice, it was decided to use NoSQL on the meeting but later on we decided on using both because it was asked. But on the implementation side it caused problems. I think it should be a good lesson that we should always follow the meetings, And if you are not confident about something and wont take responsibility, please trust other's opinion.
I tried to find APIs and resources for others to learn. Mainly focused on my individual work due to lack of knowledge.
- https://www.uuidtools.com/api/generate/v4: This API generates a UUID version 4 and returns it. There are other options available but this particular route is the one that I used.
I used /addel
for all my endpoints. Following routes adds to /addel
-
/add/resource
: POST with json body: Add resource to the database -
/add/need
: POST with json body: Add need to the database -
/add/event
: POST with json body: Add event to the database -
/add/action
: POST with json body: Add action to the database -
/get/resource
: GET: get the list of resources -
/get/need
: GET: get the list of needs -
/get/event
: GET: get the list of events -
/get/action
: GET: get the list of actions -
/get/resource?id=<id>
: GET: Get resource with id -
/get/need?id=<id>
: GET: Get need with id -
/get/event?id=<id>
: GET: Get event with id -
/get/action?id=<id>
: GET: Get action with id -
/delete/resource?id=<id>
: GET: Delete resource with id -
/delete/need?id=<id>
: GET: Delete need with id -
/delete/event?id=<id>
: GET: Delete event with id -
/delete/action?id=<id>
: GET: Delete action with id
Note: The parameters of the POST requests can be seen and be tested in docs: Swagger or Redoc. Note that these documentations also include UI endpoints as well.
For each combination of (resource, need, event, action) and (add, get, delete) there are related tests, there is no API endpoint that have not any unit test. There are total of 70 unit tests (and 1 mock test to cleanup).
For add tests, there are
- Full arguments tests
- Only required arguments tests
- 1 required argument missing tests for each required argument (should fail)
For get tests, there are
- Empty list tests
- At least 3 tests
- ID tests
- Unpresent ID tests (should return null)
For delete tests, there are
- ID tests
- Unpresent ID tests
An example test is as follows:
def test_full_arguments(self):
r = requests.post(f'http://0.0.0.0:8000{api_prefix}/add/resource', json={
"type": "food",
"location": "1234",
"notes": "this resource is here for a test",
"updated_at": "2020-04-01T12:00:00Z",
"is_active": True,
"upvotes": 0,
"downvotes": 0,
"creator_id": "test",
"creation_date": "2020-04-01T12:00:00Z",
"condition": "good",
"quantity": 1
})
# test response uuid
self.assertEqual(r.status_code, 200)
uuid = r.text[1:-1]
self.assertTrue(uuid4regex.match(uuid), f'uuid {uuid} does not match regex {uuid4regex.pattern}')
return uuid
Two types of requests have two different use of calls. POST requests require a json body, while (some of) GET requests require inline parameters. For each type of requests, the example is as follows:
import requests
uri = 'http://13.49.41.10:8000'
# POST Request Example
response = requests.post(uri + '/add/resource', json={
"type": "Food",
"location": "Istanbul",
"notes": "This is a test resource",
"updated_at": "12.05.2023",
"is_active": True,
"upvotes": 0,
"downvotes": 0,
"creator_id": "293874",
"creation_date": "12.05.2023",
"condition": "New",
"quantity": 1
})
print('Headers:', response.headers)
print('Status Code:', response.status_code)
print('Json:', response.json())
print()
# GET Request Example
response = requests.get(uri + '/get/resource?id=' + response.json())
print('Headers:', response.headers)
print('Status Code:', response.status_code)
print('Json:', response.json())
And the output is:
Headers: {'date': 'Fri, 12 May 2023 20:17:48 GMT', 'server': 'uvicorn', 'content-length': '38', 'content-type': 'application/json'}
Status Code: 200
Json: 787d8f8a-c9df-4fcc-b5fa-5c8d860dc630
Headers: {'date': 'Fri, 12 May 2023 20:17:48 GMT', 'server': 'uvicorn', 'content-length': '268', 'content-type': 'application/json'}
Status Code: 200
Json: {'uuid': '787d8f8a-c9df-4fcc-b5fa-5c8d860dc630', 'type': 'Food', 'location': 'Istanbul', 'notes': 'This is a test resource', 'updated_at': '12.05.2023', 'is_active': True, 'upvotes': 0, 'downvotes': 0, 'creator_id': '293874', 'creation_date': '12.05.2023', 'condition': 'New', 'quantity': 1}
I initially thought that we should make the dockerization and deployment by ourselves, and I did all these things. Here is the API that I deployed myself: http://13.49.41.10:8000/ (later I merged my code with shared code and docker and amazon server) The Dockerfile and other files related to the dockerization and deployment are deleted. But you can see these files in the history. As backend, I (and we) used fastapi. As database, I used PostgreSQL.
There were a lot of new things to learn in a short time, that was very very challenging. I initially tried to use MongoDB for database. I worked very hard and melted down 5 hours just struggling and I was failed. This was very depressing for me. But I recovered tomorrow, and used PostgreSQL (with the suggestions of my teammates: it is better if we could use the advantages of different types of databases).
- Opening Discussions for Web Service, APIs, Git Actions
- Note-taking in Meeting 9
- Practice App Registration with SMS implementation
- Milestone 2 Tools Discussion
- Pick which Functionality to Develop on Milestone 2
I post Twilio API twice once for sending SMS to the proper phone number. I checked them if it contains +90 for Turkey and has a length of 11 like 0532 123 45 67.
Here is the sendMessage method's code where I sent a POST method to Twilio.
Here is the result of calling this function:
However, because sending an SMS needs verified outgoing taller numbers I also post another method that adds the number to verified outgoing caller numbers. I also wrote a function to handle this. Here is the code for that:
However, because my Twilio account is on trial I couldn't post for them. Here is the response from Twilio:
On the register page I post to my created API exist, pw_not_ok, phone_nut_turkey, and registered attributes in JSON format.
In the 'exist' object I checked if the username already exists in the database, if it returns true user can not register the system because usernames must be unique.
In the 'pw_not_ok', I checked if password credentials contain at least 8 characters, if it doesn't contain at least 8 characters user couldn't register.
In the 'phone_nut_turkey', I checked if the phone number isn't suitable for Turkey, if it doesn't start with 05 and doesn't contain 11 characters user couldn't register to the system.
If a user can successfully register to the system, it first saved its credentials to the Mongo database. After that, it calls a send message method which I connected to Twilio API and sent the message and the number to be received as an SMS which I explained above.
Here is the code for my register page, where the router is APIRouter() of FastAPI which is a created API.
In the login page, I use the post method to check user credentials from the database, if they are okay user can go dummy home page by clicking login. Here is the code:
Here is the example when the credentials are not OK for login:
In the dummy home page, I used the get method with the query parameter of the username that is logged in, it goes to an endpoint with the username.
Here is the result:
Another challenge for us (especially Merve): We were using MongoDB which we have already decided on 3 weeks ago, however some of our friends used Postgre. Therefore we got an error too much. They said they would handle those errors although, they couldn't. After they couldn't handle errors especially Merve tried so hard to fix them in the last hours of subjection.
-
- Name: SendGrid API
- Route: https://api.sendgrid.com/v3/mail/send
- Description: SendGrid's web API allows our customers to pull information about their email program without having to actually log on to SendGrid.com.
-
Name: emailreport / POST
- Route: /emailreport
- Description: This function collects data from report form and sends an email about the report to the admin. In addition, it adds the report to the database.
Name: add_admin / POST (*)
- Route: /emailreport/add_admin
- Description: This function adds an admin with given email address to the database. This email address is required to submit a report.
Name: get_admin / GET (*)
- Route: /emailreport/get_admin
- Description: This function retrieve the admin's user information from the database. Reports are sent to this email address read from the database.
Name: drop_admin_table / DELETE (*)
- Route: /emailreport/drop_admin_table
- Description: This function drops admin table from the database. This function is useful if accidentally or more than one admin has been added.
def test_add_admin_success():
email = "[email protected]"
response = client.post('/emailreport/add_admin', json={"email": email})
assert response.status_code == 200
assert response.json() == {'message': 'Admin added successfully'}
def test_add_admin_failure():
email = "[email protected]"
response = client.post('/emailreport/add_admin', json={"email": email})
assert response.status_code == 500
def test_get_admin_success():
response = client.get('/emailreport/get_admin')
assert response.status_code == 200
expected_data = {
"username": "admin4report",
"first_name": "admin",
"last_name": "istrator",
"email": "[email protected]",
"phone_number": "+905554443322"
}
assert response.json() == expected_data
def test_get_admin_failure():
response = client.get('/emailreport/get_admin')
assert response.status_code == 404
expected_data = {
"detail": "Admin not found in the DB"
}
assert response.json() == expected_data
def test_drop_admin_table_success():
response = client.delete('/emailreport/drop_admin_table')
assert response.status_code == 200
expected_data = {
"message": "Admin table dropped successfully"
}
assert response.json() == expected_data
def test_drop_admin_table_failure():
response = client.delete('/emailreport/drop_admin_table')
assert response.status_code == 500
def test_emailreport_success():
report_data = {
"reporter": "John Doe",
"activity": "Activity 123",
"reason": "Spam",
"details": "This is a test report."
}
response = client.post('/emailreport', json=report_data)
assert response.status_code == 200
assert response.json()["reporter"] == "John Doe"
assert response.json()["activity"] == "Activity 123"
assert response.json()["reason"] == "Spam"
assert response.json()["details"] == "This is a test report."
def test_emailreport_failure():
report_data = {
"reporter": "John Doe",
"activity": "Activity 123",
# Missing "reason" field
"details": "This is a test report."
}
response = client.post('/emailreport', json=report_data)
assert response.status_code == 422
- emailreport
# Request
curl -X 'POST' \
'http://localhost:8000/emailreport/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"reporter": "John Doe",
"activity": "NEED352",
"reason": "Spam",
"details": "Same need sent over and over"
}'
# Response
{
"reporter": "John Doe",
"activity": "NEED352",
"reason": "Spam",
"details": "Same need sent over and over"
}
- add_admin (*)
# Request
/emailreport/add_admin
curl -X 'POST' \
'http://localhost:8000/emailreport/add_admin' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]"
}'
# Response
{
"message": "Admin added successfully"
}
- get_admin (*)
# Request
curl -X 'GET' \
'http://localhost:8000/emailreport/get_admin' \
-H 'accept: application/json'
# Response
{
"username": "admin4report",
"first_name": "admin",
"last_name": "istrator",
"email": "[email protected]",
"phone_number": "+905554443322"
}
- drop_admin_table (*)
# Request
curl -X 'DELETE' \
'http://localhost:8000/emailreport/drop_admin_table' \
-H 'accept: application/json'
# Response
{
"message": "Admin table dropped successfully"
}
-
- I tried to add the generated report to the database as well as sending an e-mail, finally solved the problem.
- I added the parts indicated with (*) late with the permission of the instructer.
-
- I wasted a lot of time looking for an unused api suitable for the project. I tried Wikidata and GoogleMaps apis but I couldn't get the result I wanted.
- Since I lost a lot of time in choosing the api, there is not enough time left for the backend and frontend part. Honestly, this part was more difficult than I thought. I had a hard time in the project as I have not used NextJS and MongoDb before.
- My teammates, especially Egecan and Merve, helped me when I got stuck. Even though I couldn't finish it on time, I learned a lot of new things.
I implemented a tweet processor that recognizes address from a given sentence or paragraph
http://ec2-13-49-231-144.eu-north-1.compute.amazonaws.com/word/word. This is the endpoint function I created. This endpoint functions calls hugging face endpoint deprem-ner endpoint
I implemented unit tests that one checks the behavior of my endpoint in case of missing required fields.
I implemented unit test that simulates the successful behavior.
I implemented unit test that returns error from server.
I really worked hard to install our fastapi server to aws. However, I also made the code modular for my teammates to work comfortably on their local pc. I also set-up ci/cd for immediate deployment and it worked without problem.
My biggest challenge was the server crashes. Whenever you miss a requirement or change the credentials of db connection the system would not respond. We couldn't communicate well through the end of semester and lack of communication ended up with some argue. However our end server functions well and I learned a lot about docker. Especially to debug-logging and managing overall server and security management.
-
- Twitter API
-
The route to create tweets is utilized:
https://api.twitter.com/2/tweets
- Posting tweet texts and chaining tweets (using the reply-to parameter) was possible
-
- Root for route is
/on-twitter
- Functions are:
-
/on-twitter/alive GET
Shows that the API is alive and available -
/on-twitter/check GET
Checks for the database connection etc. Returns OK or Failed. -
/on-twitter/getPublishedTweets/{eventId} GET
Returns the urls of the published tweets about the event described by the eventId -
/on-twitter/publishTweets/{eventId} POST
Publish the event summary messages in a tweet chain.
-
- Root for route is
-
- Only a sample: The unit test to check the availability of the ssystem
def test_alive(self):
response = self.client.get("/on-twitter/alive")
assert response.status_code == 200
assert response.json() == {'Alive': 'Yes'}
http://localhost:8000/on-twitter/getPublishedTweets/100124
http://localhost:8000/on-twitter/publishTweets/100124
After two different calls to publishTweets API function:
The twitter account is hidden for it will not be appropriate to publish tweets of disaster to public! One can follow though.
-
- Used a nosql database for the first time. It was a short experience, an introduction so to say.
- Twitter API has its own problems and troubles. "Free" features are not well defined. My intentions to make use of it in smart ways failed. I had a plan to use the Twitter Spaces (audio chat with transcripting facilitise) as a means of communication with speech to text option. There is a python project for this by the way
- For the UI staging of the API my choice was to learn and implemen next.js. It took time and was not so easy. Together with CMPE321 course project became a good opportunity though. Sample screen:
-
- Twitter API itself was a challenge
-
News API
- endpoint: https://newsapi.org/v2/everything?
- GET request
- Description:
- Authorization: key=YOUR_API_KEY. Set this parameter in the header.
- Query Parameters: I have used q, sortBy and language query parameters and send them to my external API to retrieve data according to request This API help me to get hotline news from many sources by just one API.
- Description:
-
- /news GET
- Route: /news/?subject={}&language={}&sortBy={}
- Description: This function fetches the data from newsAPI
- /news/create POST
- Route: /news/create
- Description: This function inserts the data on MongoDB .
- /news GET
-
- curl GET https://practice-app.online/api/news/?subject=erdogan&sortBy=relevance&language=en
- If OK:
- {"message": "This is news", "payload": news.json()}
- IF NOTOK:
- {"message": "Error occurred", statusCode:500}
- If OK:
- curl -d '{"title":"Some great things", "description":"lorem ipsum dolor sit amed", "subject":"BOUN", "language":"tr", "URL":"https://cmpe.boun.edu.tr"}' -H 'Content-Type: application/json' https://practice-app.online/api/news/create
- curl GET https://practice-app.online/api/news/?subject=erdogan&sortBy=relevance&language=en
-
- I was experienced on frontend, backend and devops earlier, so I took responsibility on creating basic structure of practice and write readmes for both of them.I helped my team members as far as I can. I wrote the sample API endpoints for others to see an example. I implemented MONGODB connection and schemas under the folder database. Some people from team did not follow the decisions we made in meetings, and when we warn them about they just ignored it. It created many conflicts not just in the server but also in our own local environments. I had to write all dockerfiles and docker-compose file again. Begum's external API firebase doesnot accept http requests so I bought a domain and configure the DNS settings in Cloudflare. I dockerized NGINX as a proxy due to CORS problem.
-
- Finding an external API that is both free and related to our project was a bit challenging. I prioritize to write samples for my team mates so I generally did my own work late. We had decided to use Next.js and fastAPI for like a month ago but some decided to do whatever they like. So it took lots of my time to solve this problem. Moreover One of my teammate was so rude that he did not even accept the wrong that he made and continue doing that again again. He asked for everyone to change their APIs since he cannot help in deployment. So I had to start again and write the dockerfiles. I learnt how to use Cloudflare, NGINX and configure proxies.
-
-
- get_coord
- Route: /location
- Description: This function collects coordinate values from MongoDB collection and responses it.
- insert_coord
- Route: /location/insert
- Description: This function retrieves new markers and adds it to MongoDB.
- get_coord
-
- Tests similar to the following were made during the implementation:
-
def test_locations_fetch(): response = client.get("/location/") assert response.status_code == 200 assert response.json() == [ {'username': 'ali', 'x_coord': 37.08328368718349, 'y_coord': 36.83810055490806}, {'username': 'kemal', 'x_coord': 38.934396015417725, 'y_coord': 38.32618261611021}, {'username': 'ayşe', 'x_coord': 39.915131681651594, 'y_coord': 37.35510170315383} ] def test_locations_insert(): response = client.post("/location/insert",json={"items": [{'x_coord': 38.64261790634527, 'y_coord': 37.27661132812501}]}) assert response.status_code == 200 assert response.json() == [ {'x_coord': '38.64261790634527', 'y_coord': '37.27661132812501'} ]
-
- Tests similar to the following were made during the implementation:
-
- Request:
axios.get('http://127.0.0.1:8000/location/')
- Response:
HTTP/1.1 200 OK content-type: application/json [ {'username': 'ali', 'x_coord': 37.08328368718349, 'y_coord': 36.83810055490806}, {'username': 'kemal', 'x_coord': 38.934396015417725, 'y_coord': 38.32618261611021}, {'username': 'ayşe', 'x_coord': 39.915131681651594, 'y_coord': 37.35510170315383} ]
- Request:
axios.post("/location/insert",json={"items": [{'x_coord': 38.64261790634527, 'y_coord': 37.27661132812501}]})
- Response:
HTTP/1.1 200 OK content-type: application/json [ {'x_coord': '38.64261790634527', 'y_coord': '37.27661132812501'} ]
- Request:
-
- Frontend: Creating map is very crucial for my API. I chose leaflet for it, however it was very challenging. I had to close the server rendering in order to render it on the browser.
-
- Name: OpenAQ
- Route: https://api.openaq.org/v2/latest?limit=10&page=1&offset=0
- Description: The OpenAQ API provides air quality data from various locations around the world. The API is used to fetch the latest measurements, which are then filtered and sorted as per user request.
-
- get_measurements
- Route: /aqdata
- Description: This function uses the OpenAQ API to fetch the latest air quality measurements. It also handles filtering and sorting of these measurements based on the parameters provided.
- filter_and_sort_resources_mongo
- Route: /resources
- Description: This function retrieves resources from the MongoDB collection 'Resources'. It allows filtering and sorting of these resources based on the parameters provided.
- create_resource
- Route: /resources
- Description: This function creates a new resource in the MongoDB 'Resources' collection.
- delete_resource
- Route: /resources/{resource_id}
- Description: This function deletes a specific resource in the MongoDB 'Resources' collection.
- clear_resources
- Route: /resources/clear
- Description: This function deletes all resources in the MongoDB 'Resources' collection.
- get_measurements
-
- Tests similar to the following were made during the implementation:
-
def test_get_measurements_success(): response = client.get("/aqdata") assert response.status_code == 200 def test_get_measurements_fail(): response = client.get("/aqdata", params={"country": "nonexistent"}) assert response.status_code == 404
-
- Tests similar to the following were made during the implementation:
-
- Request to /aqdata:
curl -X GET "http://localhost:8000/aqdata?country=TR&city=Denizli&sort=desc"
- Response:
HTTP/1.1 200 OK content-type: application/json [ { "country": "TR", "city": "Denizli", "measurement": "pm25", "lastUpdated": "2023-04-12T10:30:00Z" }, ... ]
- Filter & Sort request to /resources:
curl -X GET "http://localhost:8000/resources?type=book&sort_by=upvotes"
- Response:
HTTP/1.1 200 OK content-type: application/json [ { "id": "1", "type": "book", # filtered type "location": "Location1", "notes": "Notes1", "updated_at": "2023-04-12T10:30:00Z", "is_active": true, "upvotes": 10, # sorted field "downvotes": 2, "creator_id": "Creator1", "creation_date": "2023-04-12T10:30:00Z", "condition": "Good", "quantity": 5 }, { "id": "3", "type": "book", # filtered type "location": "Location1", "notes": "Notes1", "updated_at": "2023-04-12T10:30:00Z", "is_active": true, "upvotes": 16, # sorted field "downvotes": 2, "creator_id": "Creator7", "creation_date": "2023-04-10T10:30:00Z", "condition": "Good", "quantity": 4 } ... ]
- Request to /aqdata:
-
- Connecting to Server: One of the initial challenges was to establish a connection to the server. This process involved understanding how to configure the server correctly and working with different settings and configurations. It was a new experience for me and took a considerable amount of time and effort.
- API Implementation: The implementation of the API backend was another significant challenge. Understanding how to correctly use FastAPI, work with MongoDB, and manipulate data according to the requirements was not straightforward for me personally. Despite these challenges, with the collaborative effort and support from my team members, especially Egecan and Merve, I was able to overcome them. Their guidance was instrumental in understanding the concepts and executing the tasks effectively.
- Frontend: Unfortunately, due to time constraints and the complexity of the tasks, I was unable to complete the frontend for this release. This is an area I aim to focus on and improve in future projects.
📌 Communication Plan
📌 Docker and local deployment tutorial
📌 RAM
📌 Test Traceability Matrix