Marco André Rocha
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # PA: Product and Presentation ## A9: Product   **Nexus** is a generalist web-based information system for managing social interactions between users by allowing them to publish their thoughts, interests, and creative content as well as see news about their friends and people of general interest. We aim to join people that share common passions by creating themed groups where people can express themselves in a safe and prosperous environment. ### 1. Installation Below there's the link to the release with the final version of the source code in the group's Git repository: > link.com # TODO ADICIONAR A TAG DE PA The the full Docker command needed to start the image available at the group's GitLab Container Registry using the production database can be found below: > sudo docker run -it -p 8000:80 --name=lbaw2261 -e DB_DATABASE="lbaw2261" -e DB_SCHEMA="lbaw2261" -e DB_USERNAME="lbaw2261" -e DB_PASSWORD="cGtzVMep" git.fe.up.pt:5050/lbaw/lbaw2223/lbaw2261 ### 2. Usage URL to our Nexus production product: https://lbaw2261.lbaw.fe.up.pt #### 2.1. Administration Credentials | Username | Email | Password | | -------- |---------------- | -------- | | Admin | admin@gmail.com | password | Talvez mais uma pelo menos? #### 2.2. User Credentials | Type | Username | Password | | ------------- | --------- | -------- | | User | user@gmail.com | password | | Banned User | banned@example.net | password | ### 3. Application Help The Nexus user has the possibility of when he feels doubts how to use any nexus page just click on the ? to appear a mini tutorial of the page. ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/icon.png) ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/tutorial.png) ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/search.png) ### 4. Input Validation In the first photo check if the credentials are correct in this case the account does not exist or the email or password are incorrect. ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/emailerror.png) In the second photo, check if the username only contains letters and numbers and dashes. ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/emailerror.png) In the third photo, no more tags are allowed if the maximum number of tags per post is reached. ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/post.png) We have done a lot of input validation. Almost every controller that receives data has input validation. We have also made more input validations on the client. Its also worthy to note that we have done input sanitation. In this case, texts containing tags will have their tags removed from them, preventing the application from xss stored. Some more examples... ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/code1.png) ![](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/docs/code2.png) ### 5. Check Accessibility and Usability To evaluate our accessibility and usability correctness, we performed tests using 2 checklists. The results are included as PDF files in the group's repository. Links to those files can be found below: > **Accessibility:** [Score Report](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/checklists/Checklist%20de%20Acessibilidade%20-%20SAPO%20UX.pdf) > **Usability:** [Score Report](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/checklists/Checklist%20de%20Usabilidade%20-%20SAPO%20UX.pdf) Websites where the checklists used can be found: > Accessibility: https://ux.sapo.pt/checklists/acessibilidade/ > Usability: https://ux.sapo.pt/checklists/usabilidade/ We have done some popup forms with AJAX Request in Javascript. If there was no JavaScript it would still be possible to register, login, edit post and edit groups. However, we think that not having JavaScript would mostly disable every important feature of our app (including seeing the timeline and liking content) and a solution for that would implicate a bad user experience. We haven't tried to make our website printable has it did't make a lot of sense for a social network. However, the result of printing is not that bad. ### 6. HTML & CSS Validation To ensure code quality, we validated our HTML and CSS. The PDF results of the HTML and CSS code validation are present in the group's repository. The direct link to these files can be found below: > **HTML:** [Validator Report Files](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/tree/main/HTML_Validator) > **CSS:** [Validator Report File](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/HTML_Validator/CSS%20VALIDATION.pdf) In general, the results were positive. In the Html validation, there were 5 points intrisic to every page that we did no correct. Some of these seem to be from the libraries we imported. Two of them are duplicates and we found no need to fix them. In the css validation, there is nothing to point out. The only warnings given by the site were related to browser specific intructions and a warning informing that css variables were not being checked. Code validation was made using the following tools: > HTML: https://validator.w3.org/nu/ > CSS: https://jigsaw.w3.org/css-validator/ ### 7. Revisions to the Project During the development of this project, as it would be expected, many changes were made, thus changes to the previous reports were needed. For that reason, where we describe the revisions made to the project since the requirements specification stage. List of changes made: - **ER: Requirements Specification** * Changed placeholders to business rules * Recover Password US was changed to visitor * Added US regarding most reported posts/comments for the administrator * Added US: * US337 - Video Call added * US338 - Online Status - **EBD: Database Specification** * Seed updated * Triggers update (there was a need to pass the post id to some of the notification triggers) - **EAP: Architecture Specification and Prototype** - New version of the OpenAPI (called *'a9_openapi.yaml'*) was made to reflect the final list of web resources ### 8. Web Resources Specification In this final report, we updated our OpenAPI specification in YAML format to reflect the final product's web resources. Link to the `a9_openapi.yaml` file in the group's repository can be found [here](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/a9_openapi.yaml) ```yaml= openapi: 3.0.0 info: version: "2.0" title: "LBAW Nexus Web API" description: "Web Resources Specification (A9) for Nexus" servers: - url: https://lbaw2261.lbaw.fe.up.pt description: Production server - url: localhost:8000 description: Localhost server tags: - name: "M01: Authentication and Individual Profile" - name: "M02: Feed" - name: "M03: Groups" - name: "M04: Requests" - name: "M05: Messages" - name: "M06: Notifications" - name: "M07: Search" - name: "M08: User Administration and Static pages" paths: /login: get: operationId: R101 summary: "R101: Login Form" description: "Provide login form. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Ok. Show log-in UI05" post: operationId: R102 summary: "R102: Login Action" description: "Processes the login form submission. Access: PUB" tags: - "M01: Authentication and Individual Profile" requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object properties: email: # <!--- form field name type: string password: # <!--- form field name type: string required: - email - password responses: "302": description: "Redirect after processing the login credentials." headers: Location: schema: type: string examples: 302Success: description: "Successful authentication. Redirect to user profile." value: "/home" 302Error: description: "Failed authentication. Redirect to login form." value: "/login" # ================================================================== /logout: get: operationId: R103 summary: "R103: Logout Action" description: "Logout the current authenticated user. Access: USR, ADM" tags: - "M01: Authentication and Individual Profile" responses: "302": description: "Redirect after processing logout." headers: Location: schema: type: string examples: 302Success: description: "Successful logout. Redirect to login form." value: "/login" # ================================================================== /register: get: operationId: R104 summary: "R104: Register Form" description: "Provide new user registration form. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Ok. Show sign-up UI" post: operationId: R105 summary: "R105: Register Action" description: "Processes the Register form submission. Access: PUB" tags: - "M01: Authentication and Individual Profile" requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object properties: email: # <!--- form field name type: string password: # <!--- form field name type: string username: # <!--- form field name type: string birthdate: # <!--- form field name type: string bio: # <!--- form field name type: string required: - email - password - username - birthdate responses: "302": description: "Redirect after processing the register credentials." headers: Location: schema: type: string examples: 302Success: description: "Successful register. Redirect to user profile." value: "/home" 302Error: description: "Failed to register. Redirect to register form." value: "/register" # ================================================================== /google_redirect: get: operationId: R106 summary: "R106: Redirect to Google Authentication Provider" description: "Redirect to Google Authentication. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Redirect successful" # ================================================================== /callback: get: operationId: R107 summary: "R107: Google Authentication Provider Callback" description: "Return from Google Authentication Provider. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Google Login callback was successful" # ================================================================== /resetAuthPassword: get: operationId: R108 summary: "R108: Change Authenticated User's Password Page" description: "Change Authenticated User's Password. Access: USR" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Show Change Password Form UI" # ================================================================== /forgot-password: get: operationId: R109 summary: "R109: Forgot Password Form Page" description: "Forgot Password Form Page. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Show Forgot Password Form UI" # ================================================================== /forgot-password-sent: get: operationId: R110 summary: "R110: Forgot Password Sent Page" description: "Forgot Password Sent Page. Access: PUB" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Show Forgot Password Sent Page UI" # ================================================================== /reset_password_without_token: post: operationId: R111 summary: "R111: Send Password Recovery Email" description: "Send Password Recovery Email. Access: PUB" tags: - "M01: Authentication and Individual Profile" requestBody: required: true content: multipart/form-data: schema: type: object properties: email: type: string required: - email responses: "200": description: "Show Email Sent Page UI" "400": description: "Email not asscociated to any account" # ================================================================== /reset_password_with_token: post: operationId: R112 summary: "R112: Password Update" description: "Password Update Form Submission. Access: PUB" tags: - "M01: Authentication and Individual Profile" requestBody: required: true content: multipart/form-data: schema: type: object properties: password: type: string password_confirmation: type: string token: type: string required: - password - password_confirmation - token responses: "200": description: "Login into the account" "400": description: "Invalid token or password mismatch" # ================================================================== /password/reset/{token}: get: operationId: R113 summary: "R113: Get Set New Password Form" description: "Get Set New Password Form. Access: PUB" tags: - "M01: Authentication and Individual Profile" parameters: - in: path name: token schema: type: string required: true responses: "200": description: "Show Set New Password Form UI" # ================================================================== /profile/{username}: get: operationId: R114 summary: "R114: Profile page" description: "Show the profile page . Access: PUB/USR" tags: - "M01: Authentication and Individual Profile" parameters: - in: path name: username schema: type: string required: true responses: "200": description: "Ok. Show profile page" "404": description: "Profile not found" # ================================================================== /like_list/{username}: get: operationId: R115 summary: "R115: User liked content list" description: "Show User liked content. Access: USR" tags: - "M01: Authentication and Individual Profile" parameters: - in: path name: username schema: type: integer required: true responses: "200": description: "Show user liked content UI" "403": description: "Not authenticated or cannot access user liked content" # ================================================================== /comment_list/{username}: get: operationId: R116 summary: "R116 Show the comments list page" description: "Show the comments list of the user. Access: USR" tags: - "M01: Authentication and Individual Profile" responses: "200": description: "Ok. Show comments list of the user page" "403": description: "Forbidden access to resource" "404": description: "User Not found" # ================================================================== /edit_profile/{username}: get: tags: - "M01: Authentication and Individual Profile" operationId: "R117" summary: "R117: Get user edit profile page" description: "Get user edit profile page. Access: USR, ADM" parameters: - name: username in: path description: "User username" required: true schema: type: string responses: "200": description: "User profile" "404": description: "User not found" "403": description: "Forbidden" # ================================================================== /api/profile/{id}: post: operationId: R118 summary: "R118: Edit Profile" description: "Edit Profile. Access: OWN" tags: - "M01: Authentication and Individual Profile" requestBody: required: true content: application/json: schema: type: object properties: username: type: string email: type: string visibility: type: boolean birthdate: type: string bio: type: string photo: type: object # is it object? responses: "200": description: "Profile edited successfully" "403": description: "Forbidden. User is not the owner of the profile" # ================================================================== /api/profile/{username}: post: operationId: R119 summary: "R119: Edit a Profile" description: "Edit a Profile" tags: - "M01: Authentication and Individual Profile" parameters: - in: path name: username schema: type: string required: true requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string email: type: string password: type: string visibility: type: boolean bio: type: string picture: type: string format: binary responses: "200": description: "The profile was edit successfully" "403": description: "Forbidden access to resource, the profile is not yours" delete: operationId: "R120" summary: "R120: Delete a Profile" description: "Deletes a profile. Access: USR" tags: - "M01: Authentication and Individual Profile" parameters: - in: path name: username schema: type: string required: true responses: "200": description: "The profile was deleted successfully" "403": description: "Forbidden access to resource, the profile is not yours" # ================================================================== /broadcast/auth: get: tags: - "M01: Authentication and Individual Profile" operationId: "R121" summary: "RR121: Get auth in pusher" description: "Get auth token from the server so that the client can connect to pusher presence channel and make pusher requests. Access: USR" responses: "200": description: "Auth" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /: get: operationId: R201 summary: "R201: Website root redirect to home" description: "Website root. Access: PUB" tags: - "M02: Feed" responses: "302": description: "Redirect to home" headers: Location: schema: type: string examples: 302: description: "Redirect to home" value: "/home" # ================================================================== /home: get: operationId: R202 summary: "R202 View Home Page" description: "Show the home page. Access: PUB" tags: - "M02: Feed" responses: "200": description: "Ok. Show home page UI" # ================================================================== /post/{id}: get: operationId: R203 summary: "R203: View Post Page" description: "Shows post, with comments, likes etc. Access: PUB/USR" tags: - "M02: Feed" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "Ok, show Post Page UI02" "403": description: "Forbidden access to resource, the post might be private or it does not exists" # ================================================================== /api/post/feed/{type_feed}/offset/{offset}: get: operationId: R204 summary: "R204 Get Posts" description: "Gets the posts according to the parameters and returns the results as json. Access: USR and PUB for viral" tags: - "M02: Feed" parameters: - in: path name: type_feed description: 'Filter parameter. Can be "for_you", "friends", "groups", "viral" ' schema: type: string required: true - in: path name: offset description: Offset of content to show. schema: type: integer required: true responses: "200": description: "Success" content: application/json: schema: type: array items: type: object properties: id: type: integer text: type: string post_date: type: string owner: type: string photo: type: string likes_count: type: integer comments_count: type: integer ranking: type: number images: type: array items: type: string hasLiked: type: boolean isOwner: type: boolean auth: type: integer example: - id: 123 text: "The dark side of the force is stronger!!!" post_date: "2022-05-04 22:22:22" owner: Darth Vader photo: user/1.png likes_count: 10 comments_count: 5 ranking: 4.1292965 images: - post/666.png - post/667.png hasLiked: true isOwner: false auth: 0 # ================================================================== /api/comment: put: operationId: R205 summary: "R205: Edit comment" description: "Edit comment. Access: OWN" tags: - "M02: Feed" requestBody: required: true content: application/json: schema: type: object properties: id_comment: type: integer text: type: string required: - id_comment - text responses: "200": description: "The comment was edited successfully" "403": description: "Forbidden access to resource" # ================================================================== /api/comment/{id_post}: post: operationId: "R206" summary: "R206: Create Comment" description: "Create a Comment associated with a certain Post. Access: USR" tags: - "M02: Feed" parameters: - in: path name: id_post schema: type: integer required: true responses: "200": description: "Comment created successfully" "403": description: "Forbidden access to resource" # ================================================================== /api/comment/{id_comment}: delete: tags: - "M02: Feed" operationId: "R207" summary: "R207: Delete Comment" description: "Delete Comment. Access: OWN, ADM" parameters: - name: id_comment in: path description: "coment_id" required: true schema: type: string responses: "200": description: "Comment deleted" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /api/post/{id}: delete: operationId: R208 summary: "R208: Delete Post" description: "Deletes a post. Access: OWN" tags: - "M02: Feed" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "The post was deleted successfully" "403": description: "Forbidden access to resource, the post is not yours" post: operationId: R209 summary: "R209: Edit a Post" description: "Edit a post. Access: OWN" tags: - "M02: Feed" requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object properties: text: type: string picture: type: array items: type: string format: binary required: - text responses: "200": description: "The post was edit successfully" "403": description: "Forbidden access to resource, the post is not yours" # ================================================================== /api/like_post: post: operationId: "R210" summary: "R210: Toggle like on post" description: "Toggle like on post. Access: USR" tags: - "M02: Feed" requestBody: required: true content: application/json: schema: type: object properties: id_user: type: integer id_post: type: integer responses: "200": description: "The like was sucessful" "403": description: "Forbidden access to resource" # ================================================================== /api/like_comment: post: operationId: "R211" summary: "R211: Toggle like on comment" description: "Toggle like on comment. Access: USR" tags: - "M02: Feed" requestBody: required: true content: application/json: schema: type: object properties: id_user: type: integer id_comment: type: integer responses: "200": description: "The like was sucessful" "403": description: "Forbidden access to resource" # ================================================================== /api/post: post: operationId: R212 summary: "R212: Create a Post" description: "Creates a post. Access: USR" tags: - "M02: Feed" requestBody: required: true content: multipart/form-data: schema: type: object properties: text: type: string group: type: string picture: type: array items: type: string format: binary required: - text responses: "201": description: "The post was created successfully" "400": description: "There is something wrong with the request body" # ================================================================== /group/{name}: get: operationId: R301 summary: "R301: Group Profile page" description: "Group Profile. Access: PUB/USR" tags: - "M03: Groups" parameters: - in: path name: name schema: type: string required: true responses: "200": description: "Success. Show group page" "404": description: "Group Profile does not exist" # ================================================================== /group/{name}/edit: get: operationId: "R302" summary: "R302 Page to edit group" description: "Page to edit group. Access: OWN" tags: - "M03: Groups" parameters: - in: path name: name schema: type: string required: true responses: "200": description: "Show Edit Group Page UI" "403": description: "Abort. Not allowed to edit group if not owner" # ================================================================== /group/member_list/{name}: get: tags: - "M03: Groups" summary: "R303: Get group members" description: "Get group members of a group. Access: MEM, ADM (OWN of group is member)" operationId: "R303" parameters: - name: name in: path description: "Group name" required: true schema: type: string responses: "200": description: "Group members" "404": description: "Group not found" "403": description: "Forbidden" # ================================================================== /api/group/{id_group}/owner/{id_user}: post: operationId: R304 summary: "R304: Promote member to owner" description: "Promote member to owner. Access: OWN" tags: - "M03: Groups" parameters: - in: path name: id_group schema: type: integer required: true - in: path name: id_user schema: type: integer required: true responses: "200": description: "New owner added successfully" "403": description: "Forbidden access to resource. User is not owner of the group" # ================================================================== /api/group/{id_group}/member/{id_user}: delete: operationId: R305 summary: "R305: Remove a group a member" description: "Deletes a group member. Access: OWN" tags: - "M03: Groups" requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object properties: id_group: type: integer id_user: type: integer responses: "200": description: "The member was removed successfully" "403": description: "Forbidden access to resource or this member doesnt belong in this group" # ================================================================== /api/group: post: operationId: R306 summary: "R306: Create a Group" description: "Creates a Group. Access: USR" tags: - "M03: Groups" requestBody: required: true content: application/json: schema: type: array items: type: object properties: name: type: string description: type: string visibility: type: integer image: type: string format: binary responses: "201": description: "The group was created successfully" "400": description: "There is something wrong with the request body" # ================================================================== /api/group/{name}: post: operationId: R307 summary: "R307: Edit a Group" description: "Edit a Group. Access: OWN" tags: - "M03: Groups" parameters: - in: path name: name schema: type: string required: true requestBody: required: true content: multipart/form-data: schema: type: object properties: name: type: string description: type: string visibility: type: boolean picture: type: string format: binary responses: "200": description: "The group was edit successfully" "403": description: "Forbidden access to resource, the Group is not yours" delete: operationId: R308 summary: "R308: Delete Group" description: "Deletes a group. Access: OWN" tags: - "M03: Groups" parameters: - in: path name: name_group schema: type: string required: true responses: "200": description: "The group was deleted successfully" "403": description: "Forbidden access to resource, the group is not yours" # ================================================================== /api/group_member/{id}: get: operationId: R309 summary: "R309: Get group member" description: "Get group member. Access: USR" tags: - "M03: Groups" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "The group members were returned successfully" "403": description: "Forbidden access to resource" delete: operationId: R310 summary: "R310: Delete group member" description: "Delete group member from group with id. Access: OWN" tags: - "M03: Groups" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "The group member was deleted successfully" "403": description: "Forbidden access to resource" # ================================================================== /api/group/{name}/owner/{id}: post: operationId: R311 summary: "R311: Promote a Member" description: "Promote a member: OWN" tags: - "M03: Groups" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "The group member was promoted successfully" "403": description: "Forbidden access to resource" # ================================================================== /group_list/{username}: get: operationId: R312 summary: "R312: Group list of username" description: "Group list of username. Access: USR" tags: - "M03: Groups" parameters: - in: path name: username schema: type: string required: true responses: "200": description: "Success. Show group list of username page" "404": description: "Does not exist any user with this username" # ================================================================== /user/friends/requests: get: operationId: R401 summary: "R401 View friends requests" description: "Show the friends requests page.Access: USR" tags: - "M04: Requests" responses: "200": description: "Ok. Show friends requests page" "403": description: "Forbidden access to resource" "404": description: "Not found" # ================================================================== /group/{group_name}/requests: get: operationId: R402 summary: "R402: Group Requests" description: "Group Requests. Access: OWN" tags: - "M04: Requests" parameters: - in: path name: group_name schema: type: string required: true responses: "200": description: "Success. Show group requests page" "404": description: "Does not exist any Group Requests" # ================================================================== /api/user/friend/request/{id_sender}/reject: put: operationId: "R403" summary: "R403 Reject a friend request" description: "Reject a friend request. Access: USR" tags: - "M04: Requests" parameters: - in: path name: id_sender schema: type: integer required: true responses: "200": description: "The request was rejected with success" "403": description: "You need to authenticate to use this endpoint" # ================================================================== /api/group/request/{id}/send: post: operationId: "R404" summary: "R404: Send a group join request" description: "Send a group join request. Access: USR" tags: - "M04: Requests" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "The request was sent" "403": description: "You need to authenticate to use this endpoint" # ================================================================== /api/group/request/{id}: delete: operationId: R405 summary: "R405: Delete group request" description: "Delete group request. Access: OWN" tags: - "M04: Requests" parameters: - in: path name: id schema: type: integer required: true responses: "200": description: "Group request deleted successfully" "403": description: "Forbidden access to resource. User is not owner of the group" # ================================================================== /api/group/{group_name}/request/{id_sender}/accept: put: operationId: "R406" summary: "R406 Accept group join request" description: "Accept group join request. Access: OWN" tags: - "M04: Requests" parameters: - in: path name: group_name schema: type: string required: true - in: path name: id_sender schema: type: integer required: true responses: "200": description: "The request was Accepted with Success" "403": description: "You need to authenticate to use this endpoint" # ================================================================== /api/user/friend/request/{id_sender}/accept: put: operationId: R407 summary: "R407: Accept friend request" description: "Accept friend request. Access: USR" tags: - "M04: Requests" parameters: - in: path name: id_sender schema: type: integer required: true responses: "200": description: "Friend request accepted successfully" "403": description: "Forbidden. User is not the receiver of the friend request" # ================================================================== /api/group/{group_name}/request/{id_sender}/reject: put: tags: - "M04: Requests" summary: "R408: Reject request" description: "Reject group request. Access: OWN" operationId: "R408" parameters: - name: group_name in: path description: "Group name" required: true schema: type: string - name: id_sender in: path description: "Request sender id" required: true schema: type: integer responses: "200": description: "Request rejected" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /api/user/friend/request/{id_rcv}/send: post: tags: - "M04: Requests" summary: "R409: Send friend request" description: "Send friend request. Access: USR" operationId: "R409" parameters: - name: id_rcv in: path description: "Request receiver id" required: true schema: type: integer responses: "200": description: "Friend request sent" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /user/friends/{username}: get: operationId: R410 summary: "R410: View user's friend list" description: "Show the friends of user. Access: USR" tags: - "M04: Requests" responses: "200": description: "Ok. Show friends of user page" "403": description: "Forbidden access to resource" "404": description: "User Not found" # ================================================================== /messages: get: operationId: R501 summary: "R501: Show User's Messages Page" description: "Show User's Messages Page. Access: USR" tags: - "M05: Messages" responses: "200": description: "Show Message Page UI" "403": description: "Not authenticated or cannot access the messages" # ================================================================== /messages/{sender_username}: get: operationId: R502 summary: "R502 View message page" description: "Show the messages page UI04. Access: USR" tags: - "M05: Messages" responses: "200": description: "Ok. Show message page" "403": description: "Forbidden access to resource" "404": description: "Not found" # ================================================================== /api/message/{id}: post: operationId: "R503" summary: "R503: Send a message" description: "Send a message to a certain user. Access: USR" tags: - "M05: Messages" parameters: - in: path name: id schema: type: integer required: true requestBody: required: true content: application/json: schema: type: object properties: text: type: string responses: "201": description: "Successfully created" "400": description: "Text cannot be null" "403": description: "Failure" # ================================================================== /notifications: get: operationId: R601 summary: "R601: Notification Page" description: "Notification Page. Access: USR" tags: - "M06: Notifications" responses: "200": description: "Show Notification List UI" "403": description: "Not authenticated or cannot access the notifications" # ================================================================== /api/user/notifications/seen: put: operationId: "R602" summary: "R602: Mark all notifications as seen" description: "Mark all notifications as seen of a certain user. Access: USR" tags: - "M06: Notifications" responses: "200": description: "The notifications were marked as seen" "403": description: "You need to authenticate to use this endpoint" # ================================================================== /api/user/notification/{id}/seen: put: tags: - "M06: Notifications" summary: "R603: Mark notification as seen" description: "Mark notification as seen. Access: USR" operationId: "R603" parameters: - name: id in: path description: "Notification id" required: true schema: type: integer responses: "200": description: "Notification marked as seen" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /api/user/notifications: get: tags: - "M06: Notifications" summary: "R604: Get notifications" description: "Get notifications. Access: USR" operationId: "R604" responses: "200": description: "Notifications" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /api/search/{query_string}/type/{type_search}/order/{type_order}/offset/{offset}: get: operationId: R701 summary: "R701: Search API" description: "Searches for works and returns the results as JSON. Access: PUB" tags: - "M07: Search" parameters: - in: path name: query_string description: String to use for full-text search schema: type: string required: true - in: path name: type_search description: "Filter parameter can be: users, groups, posts or topics." schema: type: string required: true - in: path name: order description: "Order parameter can be: match, date, likes, comments." schema: type: integer required: true - in: path name: offset description: Offset of content to show. schema: type: integer required: true responses: "200": description: Json Search Results content: application/json: schema: type: array items: type: object # It can be users, groups, posts or topics # ================================================================== /search/{query}: get: operationId: R702 summary: "R702: Search" description: "Show Search hits page. Access: PUB" tags: - "M07: Search" parameters: - in: query name: query description: "Queried String" schema: type: string required: true responses: "302": description: "Ok. Show search page" # ================================================================== /about: get: operationId: R801 summary: "R801: About Page" description: "Show Nexus About page. Access: PUB" tags: - "M08: User Administration and Static pages" responses: "302": description: "Ok. Show about page UI" "404": description: "Page not available" # ================================================================== /contacts: get: operationId: R802 summary: "R802: Contacts Page" description: "Show Nexus Contact page. Access: PUB" tags: - "M08: User Administration and Static pages" responses: "302": description: "Ok. Show contacts page UI" "404": description: "Page not available" # ================================================================== /features: get: operationId: R803 summary: "R803: Main Features Page" description: "Show Nexus Feature page. Access: PUB" tags: - "M08: User Administration and Static pages" responses: "302": description: "Ok. Show features page UI" "404": description: "Page not available" # ================================================================== /admin: get: operationId: R804 summary: "R804: Admin Page" description: "Show administrator page. Access: ADM" tags: - "M08: User Administration and Static pages" responses: "302": description: "Ok. Show admin UI" "404": description: "Page not available" # ================================================================== /admin/statistics: get: operationId: R805 summary: "R805: Admin statistics" description: "Show Admin statistics. Access: ADM" tags: - "M08: User Administration and Static pages" responses: "200": description: "Show admin statistics UI" "403": description: "Not authenticated or cannot access admin statistics" # ================================================================== /admin/report/{username}: get: operationId: R806 summary: "R806: Get User Report" description: "Get User Report. Access: ADM" tags: - "M08: User Administration and Static pages" parameters: - in: path name: username schema: type: string required: true responses: "200": description: "Show User Report UI" "403": description: "Not authenticated or cannot access user report" # ================================================================== /api/report/reject_all/{userID}: put: operationId: "R807" summary: "R807: Reject all pending reports from a user" description: "Reject all pending reports from a user. Access: ADM" tags: - "M08: User Administration and Static pages" parameters: - in: path name: userID schema: type: integer required: true responses: "200": description: "Success" # ================================================================== /api/report: put: tags: - "M08: User Administration and Static pages" summary: "R808: Report user" description: "Report user. Access: ADM" operationId: "R808" requestBody: description: "Report" required: true content: application/json: schema: type: object properties: decision: type: integer id: type: integer responses: "200": description: "Report sent" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" post: operationId: "R809" summary: "R809: Create a report" description: "Create a report of a post or comment. Access: USR" tags: - "M08: User Administration and Static pages" requestBody: required: true content: application/json: schema: type: object properties: id_post: type: integer id_comment: type: integer description: type: string responses: "200": description: "Success" content: application/json: schema: type: object properties: id_reporter: type: integer id_admin: type: integer report_date: type: string description: type: string decision: type: string id_post: type: integer id_comment: type: integer example: - id_reporter: 1 id_admin: null report_date: "2022-05-04 22:22:22" description: "It's offensive" decision: "Pendent" id_post: 5 id_comment: null # ================================================================== /api/user/ban/{userID}/{time_option}: put: tags: - "M08: User Administration and Static pages" summary: "R810: Ban user" description: "Ban user. Access: ADM" operationId: "R810" parameters: - name: userID in: path description: "User ID" required: true schema: type: integer - name: time_option in: path description: "Time option" required: true schema: type: integer oneOf: - minimum: 0 maximum: 8 responses: "200": description: "User banned" "400": description: "Invalid time option" "403": description: "Forbidden" "419": description: "Not a valid CSRF token" # ================================================================== /api/admin/pendent_reports/{query_string}: get: operationId: "R811" summary: "R811: Get the pendent reports" description: "Get the pendent reports. Access: ADM" tags: - "M08: User Administration and Static pages" parameters: - in: path name: query_string schema: type: string required: true responses: "200": description: "Success" content: application/json: schema: type: array items: type: object properties: id: type: integer username: type: string photo: type: string ban_date: type: string decision_date: type: string "403": description: "Forbidden" # ================================================================== /api/admin/past_reports/{query_string}: get: operationId: "R812" summary: "R812: Get the past reports" description: "Get the past reports. Access: ADM" tags: - "M08: User Administration and Static pages" parameters: - in: path name: query_string schema: type: string required: true responses: "200": description: "Success" content: application/json: schema: type: array items: type: object properties: id: type: integer username: type: string photo: type: string ban_date: type: string decision_date: type: string "403": description: "Forbidden" # ================================================================== /api/user/friend/{id}: get: operationId: R813 summary: "R813: User friends" description: "Saw the user:USR" tags: - "M08: User Administration and Static pages" parameters: - in: path name: id description: "User" schema: type: string required: true responses: "200": description: "Success show the user friends page" "403": description: "Forbidden access to resource" "404": description: "User not found" # ================================================================== ``` **Note:** Administrator can make all CRUD operations in any user profile content. ### 9. Implementation Details #### 9.1. Libraries Used In this Nexus Project we used many libraries, frameworks and services: | Library | Description | Link to Example | |:--------------------------------------------------- |:---------------------------------------------------------------------------------------------------------- |:---------------: | | [Bootstrap](https://getbootstrap.com/) | Bootstrap was used to built the frontend of the entire Nexus Website. | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/resources/views/layouts/app.blade.php#L16) | | [Font Awesome](https://fontawesome.com/) | Font Awesome was used for good and consistent icons across the entire Nexus Website | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/resources/views/partials/sidebar/left_sidebar.blade.php#L10) | | [Carbon](https://carbon.nesbot.com/) | Carbon was used for date/time formatting in posts, comments and messages | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/resources/views/partials/post_item.blade.php#L18) | | [Socialite](https://laravel.com/docs/9.x/socialite) | Socialite was used for Google Auth Sign in | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/app/Http/Controllers/Auth/LoginController.php#L57) | | [Pusher](https://pusher.com/) | Pusher was used to create realtime notifications and serve as a presence channel for siggnaling videocalls | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/public/js/app.js#L4) | | [Mailtrap](https://mailtrap.io/) | Mailtrap alows for password recovery mail sending/receiving | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/app/Http/Controllers/PasswordRecoverController.php#L57) | | [IntroJS](https://introjs.com/) | To create step-by-step guided tutorials to serve as a contextual help for our users | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/public/js/app.js#L333) | | [WebRTC API](https://webrtc.org/) | For creating Peer-to-peer comunications to share audio and video | [Link](https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/blob/main/public/js/app.js#L120) | #### 9.2 User Stories > This subsection should include all high and medium priority user stories, sorted by order of implementation. Implementation should be sequential according to the order identified below. > > If there are new user stories, also include them in this table. > The owner of the user story should have the name in **bold**. > This table should be updated when a user story is completed and another one started. A version of the board that was used during the development of the project can be found here: > https://git.fe.up.pt/lbaw/lbaw2223/lbaw2261/-/boards | **Identifier** | **Name** | **Module** | **Priority** | **Team Members** | **State** | |:--------------:|:------------------------------- |:------------------------------------------ |:------------:|:-------------------:|:---------:| | US101 | View Public Timeline | M02: Feed | High | **João** | 100% | | US102 | View Public Profiles | M01: Authentication and Individual Profile | High | **David** | 100% | | US103 | Search for Public Profiles | M07: Search | High | **João** | 100% | | US104 | Exact Match Search | M07: Search | High | **João** | 100% | | US105 | Full-text Search | M07: Search | High | **João** | 100% | | US106 | About US | M08: User Administration and Static pages | High | **David** | 100% | | US107 | Main Features | M08: User Administration and Static pages | High | **David** | 100% | | US108 | Contacts | M08: User Administration and Static pages | High | **David** | 100% | | US109 | Search over Multiple Attributes | M07: Search | Medium | **João** | 100% | | US110 | Search Filters | M07: Search | Medium | **João** | 100% | | US111 | Search for Content | M07: Search | Medium | **João** | 100% | | US112 | Search by Topic | M07: Search | Medium | **João** | 100% | | US113 | Order by Date | M07: Search | Medium | **João** | 100% | | US201 | Login | M01: Authentication and Individual Profile | High | **Ricardo** | 100% | | US202 | Register | M01: Authentication and Individual Profile | High | **Ricardo** | 100% | | US203 | Recover Password | M01: Authentication and Individual Profile | Medium | **Marco** | 100% | | US301 | Logout | M01: Authentication and Individual Profile | High | **Ricardo** | 100% | | US302 | View my profile | M01: Authentication and Individual Profile | High | **David** | 100% | | US303 | Edit Profile | M01: Authentication and Individual Profile | High | **Marco**, Ricardo | 100% | | US304 | View Personalized Timeline | M02: Feed | High | **João** | 100% | | US305 | View Friends Profiles/Feed | M02: Feed | High | **Marco** | 100% | | US306 | Make a Post | M02: Feed | High | **Ricardo** | 100% | | US307 | Edit Post | M02: Feed | High | **Marco**, Ricardo | 100% | | US308 | Delete Post | M02: Feed | High | **Ricardo** | 100% | | US309 | View Personal notifications | M06: Notifications | Medium | **Ricardo** | 100% | | US310 | Manage friend Requests | M04: Requests | Medium | **Ricardo** | 100% | | US311 | Manage Friends | M04: Requests | Medium | **Ricardo**, Marco | 100% | | US312 | Make Comments | M02: Feed | Medium | **Marco** | 100% | | US313 | React to Post | M02: Feed | Medium | **Marco** | 100% | | US314 | React to Comment | M02: Feed | Medium | **Marco** | 100% | | US315 | Create groups | M03: Groups | Medium | **Marco**, Ricardo | 100% | | US316 | Request to Join Group | M03: Groups | Medium | **Ricardo** | 100% | | US317 | Delete account | M01: Authentication and Individual Profile | Medium | **Ricardo** | 100% | | US318 | See Reactions | M02: Feed | Medium | **Marco** | 100% | | US319 | Make Reports | M08: User Administration and Static pages | Medium | **Marco** | 100% | | US320 | Likes Notification | M06: Notifications | Medium | **Ricardo** | 100% | | US321 | Comments Notification | M06: Notifications | Medium | **Ricardo** | 100% | | US322 | Friend Requests Notification | M06: Notifications | Medium | **Ricardo** | 100% | | US323 | Edit Comment | M02: Feed | Medium | **Marco** | 100% | | US324 | Delete Comment | M02: Feed | Medium | **Marco** | 100% | | US325 | Reply to Comments | M02: Feed | Medium | **Marco** | 100% | | US326 | Timeline Filter | M02: Feed | Medium | **João** | 100% | | US327 | Post Topics | M02: Feed | Medium | **Marco** | 100% | | US328 | Personalized Recommendations | M02: Feed | Medium | **Ricardo** | 100% | | US329 | Direct Messages | M05: Messages | Medium | **Ricardo** | 100% | | US401 | Administer User Accounts | M08: User Administration and Static pages | High | **Marco**, Ricardo | 100% | | US402 | Administrator Accounts | M08: User Administration and Static pages | Medium | **David** | 100% | | US403 | Remove User Posts | M08: User Administration and Static pages | Medium | **Marco** | 100% | | US404 | (un)Ban User Accounts | M08: User Administration and Static pages | Medium | **Marco** | 100% | | US405 | Receive Reports | M08: User Administration and Static pages | Medium | **Marco**, João | 100% | | US501 | Edit Group Information | M03: Groups | High | **Marco**, Ricardo | 100% | | US502 | Remove Member | M03: Groups | Medium | **Marco** | 100% | | US503 | Manage Join Requests | M03: Groups | Medium | **Ricardo** | 100% | | US504 | Transfer Ownership | M03: Groups | Medium | **David** | 100% | | US505 | Remove Post From Group | M03: Groups | Medium | **Marco** | 100% | | US601 | View Group’s Members | M03: Groups | High | **Marco** | 100% | | US602 | Post on Group | M03: Groups | High | **Marco**, Ricardo | 100% | | US603 | Leave Group | M03: Groups | High | **Marco** | 100% | *Table 1: Nexus User Stories (High and Medium priority)* | **Identifier** | **Name** | **Module** | **Priority** | **Team Members** | **State** | |:--------------:|:---------------------------- |:------------------------------------------ |:------------:|:----------------:|:---------:| | US114 | Ordering of Results | M07: Search | Low | **João** | 100% | | US337 | Video Call | M05: Messages | Low | **Ricardo** | 100% | | US406 | Posts/Comments Most Reported | M08: User Administration and Static pages | Low | **Marco**, João | 100% | | US506 | Change Group Visibility | M03: Groups | Low | **Marco** | 100% | | US701 | Register using API | M01: Authentication and Individual Profile | Low | **Marco** | 100% | | US702 | Login using API | M01: Authentication and Individual Profile | Low | **Marco** | 100% | | US338 | Online Status | M05: Messages | Low | **Ricardo** | 100% | | US330 | Account Verification | M01: Authentication and Individual Profile | Low | N/A | 0% | | US331 | Birthday Notifications | M06-Notifications | Low | N/A | 0% | | US332 | Delete Messages | M05: Messages | Low | N/A | 0% | | US333 | Polls | M02: Feed | Low | N/A | 0% | | US334 | Manage Post Visibility | M02: Feed | Low | N/A | 0% | | US335 | Tag friends | M02: Feed | Low | N/A | 0% | | US336 | Block User | M08: User Administration and Static pages | Low | N/A | 0% | | US507 | Group Announcements | M03: Groups | Low | N/A | 0% | *Table 2: Nexus User Stories (Low priority)* --- ## A10: Presentation ### 1. Product presentation **Nexus** is a revolutionary new social network that connects people from all walks of life. With a sleek and intuitive interface, users can easily create profiles, share their interests and passions by making posts, comments and likes and by creating or joining pre-existing thematic groups to connect with friends and family. This communication is further facilitated by Nexus built-in chat with possibility to make video calls so you can be closer to the ones you love. URL to the final product: > https://lbaw2261.lbaw.fe.up.pt ### 2. Video presentation To showcase our product's main features, we created a short 2 minute MP4 video that was uploaded to Moodle. The presentation video can also be found [here](TODO.COM) ## Revision history Changes made during the first submission: 1. Added A9 2. Added A10 5. PA Revision GROUP2261, 01/01/2023 - David Ferreira, up202006302@g.uporto.pt - João Alves, up202007614@g.uporto.pt - Marco André, up202004891@g.uporto.pt (Editor) - Ricardo Matos, up202007962@g.uporto.pt

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully