# Battleship online game ## API DESIGN ### Common errors * Fail for all POST request - Invailid request (not json len_ship / wrong format input): 400 BAD REQUEST ``` { "errors": { "username": "2 is not of len_ship 'string'" }, "message": "Input payload validation failed" } ``` * Fail for all wrong JWT authorization requset: 401 UNAUTHORIZED ``` { "status": "fail", "message": "Invalid token. Please log in again." } ``` ### 1. User related operations #### 1.1. Create new user ##### Request * Path: /users * Method: POST * Header: * Content-Type: application/json * Body: ``` { "email": "string", "username": string", "password": "string" } ``` ##### Response * Success: 201 CREATED ``` { "status": "success", "message": "Successfully registered.", "Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTY0NDA3MjIsImlhdCI6MTU1NjM1NDMxNywic3ViIjo0fQ.bxGE4ZUwRQgoJtzjnNd2QQQInlokG15ow3j3tNuPkpk" } ``` * Fail - Existed user: 409 CONFLICT ``` { "status": "fail", "message": "User already exists. Please Log in." } ``` * Fail - Existed user: 400 BAD REQUEST ``` { "errors": { "password": "'password' len_shipgth must not be an empty string" }, "message": "Input payload validation failed" } ``` #### 1.2. Get all users' information ##### Request * Path: /users * Method: GET ##### Response * Success: 200 OK ``` { "status": "success", "data": [ { "username": "user1", "email": "user1@email.com", "public_id": "45f86c52-3c02-4ee3-96a5-9c702e833ca1", "admin": true }, { "username": "user2", "email": "user2@email.com", "public_id": "57371633-6eab-4818-8a25-cb58a064605f", "admin": false } ] } ``` #### 1.3. User get his/her own information ##### Request * Path: /users/my_account * Method: GET * Header: * Authorization: Bearer JWT ##### Response * Success: 200 OK ``` { "status": "success", "data": { "username": "user1", "email": "user1@email.com", "public_id": "45f86c52-3c02-4ee3-96a5-9c702e833ca1", "admin": true } } ``` #### 1.4. User update his/her own information ##### Request * Path: /users/my_account * Method: PUT * Header: * Authorization: Bearer JWT * Content-Type: application/JSON * Body: ``` { "email": "user2@email.com", "username": "user2", "bio": "this is my bio" } ``` ##### Response * Success: 200 OK ``` { "status": "success", "message": "Successfully update profile." } ``` * Fail - Email address has been taken : 409 CONFLICT ``` { "status": "fail", "message": "Email address has been taken." } ``` #### 1.5. User update his/her own password ##### Request * Path: /users/my_account/new_password * Method: PUT * Header: * Authorization: Bearer JWT * Content-Type: application/JSON * Body: ``` { "old_password": "str", "new_password": "string" } ``` ##### Response * Success: 200 OK ``` { "status": "success", "message": "Successfully update new password." } ``` * Fail - Old password not right 401 Unauthorized ``` { "status": "fail", "message": "wrong old password" } ``` #### 1.6. Get user's infromation by given public id ##### Request * Path: /users/<user_public_id> * Method: GET ##### Response * Success ``` { "status": "success", "data": { "username": "user1", "email": "user1@email.com", "public_id": "45f86c52-3c02-4ee3-96a5-9c702e833ca1", "admin": true } } ``` #### 1.7. User follow other user ##### Request * Path: /users/followings/<public_id> * Method: POST * Header: * Authorization: Bearer JWT * Body: ``` ``` ##### Response * Success: 200 ``` { "status": "success", "message": "create friendship successfully" } ``` * Fail - You follow yourself: 400 ``` { "status": "fail", "message": "you can not follow your self" } ``` #### 1.8. User unfollow other user ##### Request * Path: /users/friendships/<public_id> * Method: DELETE * Header: * Authorization: Bearer JWT * Body: ##### Response * Success: 200 ``` { "status": "success", "message": "delete friendship successfully" } ``` * Fail - The friendship (follow) does not exist ``` { "status": "fail", "message": "the friendship does not exist" } ``` #### 1.9. Get all followers ##### Request * Path: /users/followers * Method: GET * Header: * Authorization: Bearer JWT * Body: ##### Response * Success: 200 ``` { "status": "success", "data": { "1": { "username": "user1", "email": "user1@email.com", "public_id": "12128039-b394-4a99-949b-4848421659cf", "admin": false, "bio": "this is my bio" } } } ``` #### 1.10. Get all followings ##### Request * Path: /users/followings * Method: GET * Header: * Authorization: Bearer JWT * Body: ##### Response * Success: 200 ``` { "status": "success", "data": [ { "username": "user1", "email": "user1@email.com", "public_id": "12128039-b394-4a99-949b-4848421659cf", "admin": false, "bio": "this is my bio" } ] } ``` ### 2. Authentication related operations #### 2.1. Login user ##### Request * Path: /auth * Method: POST * Header: * Content-Type: application/json * Body: ``` { "email": "user5@email.com", "password": "string" } ``` ##### Response * Success: 200 OK ``` { "status": "success", "message": "Successfully logged in.", "Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTY0NjA0MDQsImlhdCI6MTU1NjM3Mzk5OSwic3ViIjo1fQ.9XODzRnoxP4QzN7JtWJM68FDR3OR0IW_x0TgzBjU1bs" } ``` * Fail - Wrong password or email: 401 UNAUTHORIZED ``` { "status": "fail", "message": "email or password does not match." } ``` #### 2.2. Logout user with token ##### Request * Path: auth * Method: DELETE * Header: * Authorization: Bearer JWT ##### Response * Success: ``` { "status": "success", "message": "Successfully logged out." } ``` ### 3. Messages related operations #### 3.1. Get list of chat friends ##### Request * Path: /messages * Method: GET * Header: * Authorization: Bearer JWT ##### Response * Success ``` { "status": "success", "list_chat_friends": [ { "username": "viet anh", "email": "vietanh@email.com", "public_id": "10003", "admin": false, "bio": null } ] } ``` #### 3.2. Get personal messages with given user/room public id ##### Request * Path: /messages/57371633-6eab-4818-8a25-cb58a064605f?offset=0&limit=2 ( offset and limit to get newest message with timestamp ) * Method: GET * Header: * Authorization: Bearer JWT ##### Response * Success ``` { "status": "success", "data": [ { "content": "Hello User 1", "sender_public_id": "57371633-6eab-4818-8a25-cb58a064605f", "timestamp": "1555485391.0" }, { "content": "Hello user 2", "sender_public_id": "45f86c52-3c02-4ee3-96a5-9c702e833ca1", "timestamp": "1555485379.0" } ] } ``` #### 3.3. Send friend an invitation link ##### Request * Path: /messages/invitations/<rival_public_id> * Method: POST * Header: * Authorization: Bearer JWT * Body ``` { "invitation_link": "adsfasjdfuwe.com" } ``` ##### Response * Success: 200 ``` { "status": "success", "message": "send invitation successfully" } ``` ### 4. Game related operations #### 4.1. List all games ##### Request * Path: /games * Method: GET ##### Response * Success ``` { "status": "success", "data": [ { "public_id": "6611ed5d-e11b-4ace-bea3-c9bd1432ac9b", "name": "game_1", "num_players": 2 } ] } ``` #### 4.2. List all games ##### Request * Path: /games * Method: GET ##### Response * Success: 200 ``` { "status": "success", "data": [ { "public_id": "battle_ship", "name": "Battle Ship", "num_players": 2 } ] } ``` #### 4.3. List all rooms ##### Request * Path: /games/rooms?offset=0&limit=4 * Method: GET ##### Response * Success: 2 ``` { "status": "success", "data": [ { "room_public_id": "e447495e-0aa1-4fe6-a926-9d6d04e0a69a", "game": { "name": "Battle Ship", "public_id": "battle_ship" }, "players": [ { "username": "sỹ an", "email": "syan@email.com", "public_id": "10002", "admin": false, "bio": null, "creator": true }, { "username": "viet anh", "email": "vietanh@email.com", "public_id": "10003", "admin": false, "bio": null, "creator": false } ], "created_at": "1558213461.0" } ] } ``` #### 4.4. Create new room ##### Request * Path: /games/rooms * Method: POST * Header: * Authorization: Bearer JWT * Body: ``` { "game_public_id": "string" } ``` ##### Response * Success: 201 CREATED ``` { "status": "success", "message": "Create room successfully", "room_public_id": "2cc3addb-fb87-4fa5-9873-eaad8385478c" } ``` * Fail - Invalid game public id: 400 BAD REQUEST ``` { "status": "fail", "message": "Invalid game identifier" } ``` #### 4.5. Get a room's information by public id (When the room does not have enough player number, the first one enter the link will be added automtically) ##### Request * Path: /games/rooms/<room_public_id> * Method: GET * Header: * Authorization: Bearer JWT ##### Response * Success: 200 OK ``` { "status": "success", "joined": false, "data": { "room_public_id": "f98ec056-cbe2-4498-97ae-91a3c975ba0d", "game": { "name": "Battle Ship", "public_id": "battle_ship" }, "players": [ { "username": "sỹ an", "email": "syan@email.com", "public_id": "10002", "admin": false, "bio": null, "creator": false }, { "username": "viet anh", "email": "vietanh@email.com", "public_id": "10003", "admin": false, "bio": null, "creator": true } ], "created_at": "1558340151.0" } } ``` - `joined`: user joined room at this time? #### 4.6. Ranking system ##### Request * Path: /games/rankings?delta=<time_delta>&top=<top> Phần này phải điền time delta dưới dạng milisecond và số lượng trong top, ví dụ top 5 hay top 10 1 tuần thì delta = 604800000 1 tháng thì delta = 2629746000 1 năm thì delta = 31556952000 * Method: POST * Header: * Authorization: Bearer JWT ##### Response * Success: 200 OK ``` { "data": [ { "username": "vanh", "email": "vanh@email.com", "public_id": "10003", "admin": false, "bio": null, "num_win": 2, "num_total": 3 } ] } ``` ## SOCKET DESIGN ### Socket login and get private message #### Step 1: User enter message area and connect to the socket: ``` var address = 'http://127.0.0.1:5000' var socket = io.connect(address); ``` #### Step 2: User have to verify jwt token: * Request login: ``` request_object = { 'authorization': token }; rooms.emit('request_login', request_object) ``` * Response login: ``` rooms.on('response_login', function(response_object){ // TODO // response_object = {"status":"success","message":"Authenticate successfully"} // response_object = {'status': fail, 'message': 'Fail to authenticate'} } ``` #### Step 3: User get the list of online User get the list of online followings: ``` rooms.on('online_followings', function(response_object){ // TODO // response_object = {"online_follwings":[{"username":"sỹ an","email":"syan@email.com","public_id":"10002","admin":false,"bio":null}]} }); ``` #### Step 4: User send and listen to messages User can send message: ``` request_object = { 'receiver_public_id': receiver_public_id, 'content': content } rooms.emit('send_message', request_object); ``` User listen to the messages: ``` rooms.on('receive_message', function(response_object){ // TODO // response_object = {"content":"dfsaf", "sender_public_id":"10002", "created_at":"1558471157.0"} } ``` ### Game Room #### Step 1: User enter room and connect to the socket: ``` var address = 'http://127.0.0.1:5000' var rooms = io.connect(address + '/rooms'); ``` #### Step 2: User have to verify jwt token and room id for login: * Request login: ``` request_object = { 'authorization': token, 'room_public_id': room_public_id }; rooms.emit('request_login', request_object) ``` * Response login: ``` rooms.on('response_login', function(response_object){ // TODO // response_object = {"status":"success","message":"Authenticate successfully"} // response_object = {'status': fail, 'message': 'Fail to authenticate'} // response_object = {'status': fail, 'message': 'Room not found'} } ``` #### Step 3: User get the list of online ``` rooms.on('users_in_room', function(response_object){ // TODO // response_object = {"users_in_room":[{"username":"sỹ an","email":"syan@email.com","public_id":"10002","admin":false,"bio":null}]} }); ``` #### Step 4a: Sending messages and listen to messages User can send message in room: ``` request_object = { 'content': content } rooms.emit('send_message', request_object); ``` User listen to the messages in room: ``` rooms.on('new_message', function(response_object){ // TODO // response_object = {"sender_public_id":"10002", "content":"Hello everyone in Room", "created_at": "1558420893.0"} } ``` #### Step 4b: Sending commands and listen to events Only players of that room can send the command: (we will see list of commands later) Xem danh sách các command ở dưới, bao gồm command tạo mới tàu và bắn ``` request_object = { 'command': command } rooms.emit('request_command', request_object); ``` User listen to event of that room: (we will see list of events later) Xem danh sách các event ở dưới, bao gồm event update thông tin board đang chơi ``` rooms.on('receive_event', function(response_object){ // TODO // response_object = {"event": event} } ``` #### List of commands: * Save ships: Chỉ có người chơi có thể save ships, gồm 10 ships, 4 tàu loại 1, 3 tàu loại 2, 2 tàu loại 3 và 1 tàu loại 4 ``` { "name": "save_ships", "ships": [ {"x": 6, "y": 2, "vertical": true, "len_ship": 1}, {"x": 6, "y": 4, "vertical": true, "len_ship": 1}, {"x": 6, "y": 6, "vertical": true, "len_ship": 1}, {"x": 6, "y": 8, "vertical": true, "len_ship": 1}, {"x": 3, "y": 1, "vertical": false, "len_ship": 2}, {"x": 3, "y": 3, "vertical": true, "len_ship": 2}, {"x": 3, "y": 6, "vertical": true, "len_ship": 2}, {"x": 9, "y": 7, "vertical": true, "len_ship": 3}, {"x": 0, "y": 9, "vertical": false, "len_ship": 3}, {"x": 0, "y": 0, "vertical": true, "len_ship": 4} ] } ``` * Shoot: (NOT DONE YET) ``` { "name": "shoot", "x": 3, "y": 0 } ``` #### List of events: * Update boards: Update the board when: * Player or user connect to room and get update information * Next turn Event update the board: Các thông tin chính sẽ được update thông qua đây. Cả người chơi và người xem đều lấy thông tin qua đây. Nếu bạn là người chơi, bạn có thể xem tất cả các tàu của bạn ('sink' là true hoặc false). Còn nếu bạn là người xem hoặc đối thủ, bạn chỉ có thể xem các tàu với 'sink' là true. board là 1 ma trận 2D 10*10. 0 là chưa bị bắn, -1 là bắn hụt, 1 là bị bắn. ``` { "name": "update_boards", "is_enough_players": true, "is_player": true, "is_winner": false, "game_over": false, "turn": <user_public_id>/null, "boards": [ { "user_public_id": <user_public_id_1>, "board": [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ... ,[0,0,0,0,0,0,0,0,0,0] ], "ships_ready": true, "ships": [ {"x": 6, "y": 2, "vertical": true, "len_ship": 1, "sink": true}, {"x": 6, "y": 4, "vertical": true, "len_ship": 1, "sink": true} ], "turn": true }, { "user_public_id": <user_public_id_2>, "board": [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ... ,[0,0,0,0,0,0,0,0,0,0] ], "ships_ready": true, "ships": [ {"x": 6, "y": 2, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 4, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 6, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 8, "vertical": true, "len_ship": 1, "sink": false}, {"x": 3, "y": 1, "vertical": false, "len_ship": 2, "sink": false}, {"x": 3, "y": 3, "vertical": true, "len_ship": 2, "sink": false}, {"x": 3, "y": 6, "vertical": true, "len_ship": 2, "sink": false}, {"x": 9, "y": 7, "vertical": true, "len_ship": 3, "sink": false}, {"x": 0, "y": 9, "vertical": false, "len_ship": 3, "sink": false}, {"x": 0, "y": 0, "vertical": true, "len_ship": 4, "sink": false} ], "turn": False } ] } ``` update_boards có thể được thực hiện bằng cách gọi manual. ``` rooms.emit('request_update'); ``` * history format in data (Vanh don't need to read this) * ``` 'hist': [ { 'user_id': <user_id_1> 'board': [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ... ,[0,0,0,0,0,0,0,0,0,0] ], 'ships': [ {"x": 6, "y": 2, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 4, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 6, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 8, "vertical": true, "len_ship": 1, "sink": false}, {"x": 3, "y": 1, "vertical": false, "len_ship": 2, "sink": false}, {"x": 3, "y": 3, "vertical": true, "len_ship": 2, "sink": false}, {"x": 3, "y": 6, "vertical": true, "len_ship": 2, "sink": false}, {"x": 9, "y": 7, "vertical": true, "len_ship": 3, "sink": false}, {"x": 0, "y": 9, "vertical": false, "len_ship": 3, "sink": false}, {"x": 0, "y": 0, "vertical": true, "len_ship": 4, "sink": false} ] }, { 'user_id': <user_id_1> 'board': [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ... ,[0,0,0,0,0,0,0,0,0,0] ], 'ships': [ {"x": 6, "y": 2, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 4, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 6, "vertical": true, "len_ship": 1, "sink": false}, {"x": 6, "y": 8, "vertical": true, "len_ship": 1, "sink": false}, {"x": 3, "y": 1, "vertical": false, "len_ship": 2, "sink": false}, {"x": 3, "y": 3, "vertical": true, "len_ship": 2, "sink": false}, {"x": 3, "y": 6, "vertical": true, "len_ship": 2, "sink": false}, {"x": 9, "y": 7, "vertical": true, "len_ship": 3, "sink": false}, {"x": 0, "y": 9, "vertical": false, "len_ship": 3, "sink": false}, {"x": 0, "y": 0, "vertical": true, "len_ship": 4, "sink": false} ] } ], 'turn': <user_id> ```