# SobSession
π A place for people who love a good cry π

---
## Project Intro
It works! View the deployed site here:
https://sob-sessions-2.fly.dev/
---
## Our Roles
- Beth: :palm_tree: SCRUM Facilitator :palm_tree:
- Tom: :package: DevOps :package:
- Mark: :pager: QA :pager:
- Taha: :panda_face: UX :panda_face:
---
## SCRUM facilitator
- Facilitate stand ups
- Discuss roles and tasks with the team
- Record progress by updating the kanban board/ the whiteboard in person
---
## DevOps
OAuth.
---
## QA
* Agreed overarching conventions (e.g. single quotes, tab spaces...)
* Two approvals before merge to main
* Set up eslint and prettier and incorporated into workflow
* However this :point_up: wasn't set up until day two so had nearly 50 linting issues to work through. Next time do it at project start as a chore
* Did not automate linting/prettier with e.g. husky - didn't fancy the gitHassle
* Set up jest for testing and used supertest to manage http requests
* Worked well and removed the need for the helpers.js file and made for readable and concise testing code
---
## UX
* Wireframe using [tldraw](https://www.tldraw.com/r/v2_E2B4vhDM-y3bh5FhSJRXa?viewport=335%2C-30%2C1755%2C1005&page=page%3A9VhkqMKi6LCu7kKg2lkFD)
* Dark Mode
* Stars input slider
* Component structure
* Input form still quite ugly :zombie:
* Couldn't add usernames to ratings :cry:
---
### Setbacks π§
* Using ES6 modules cost us development time to sort out unpredicatble issues with some node modules like dotenv, jest
*
---
### Something weβre proud of
#### Sanitise function
```javascipt=
export function sanitiseInput(input) {
const regex = /[&<>"']/g;
const replacements = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
const sanitisedInput = input.replace(
regex,
match => replacements[match] || ''
);
return sanitisedInput;
}
```
#### Example jest test
```javascript=
import request from 'supertest';
import server from '../src/server';
describe('GET home route handler', () => {
it('responds with an html tag ', async () => {
const response = await request(server).get('/');
expect(response.status).toBe(200);
expect(response.text).toMatch(/<html/);
});
it('responds with the sobSessions logo text', async () => {
const logoText = 'ΰ²₯_ΰ²₯';
const response = await request(server).get('/');
expect(response.text).toContain(logoText);
});
});
```
---
## Velocity :fast_forward:
On the secon day, we used velocity predictions for smaller tasks

---
## Aria-Label
```javascript
return `<figure aria-label="song rated ${number} star${
number === 1 ? 's' : ''
}">${rating}</figure>`;
```
---
### Paradigms
#### Imperative
```javascript=
export default function stars(number) {
const goldStars = number;
const greyStars = 5 - number;
let rating = ``;
for (let i = 0; i < goldStars; i++) {
rating += `<i class="fa-solid fa-star" style="color: #d7a63c;" aria-hidden="true"></i>`;
}
for (let i = 0; i < greyStars; i++) {
rating += `<i class="fa-solid fa-star" style="color: #636363;" aria-hidden="true"></i>`;
}
return `<figure aria-label="song rated ${number} star${
number > 1 ? 's' : ''
}">${rating}</figure>`;
}
```
---
#### Declarative
```javascript
describe('GET home route handler', () => {
it
//some code
}));
```
#### Reactive
```javascript
const content = /*html*/ `
${header()}
<main class="all-songs-container">
<h1> ${req.signedCookies.name} </h1>
${songs}
</main>
`;
```
### CLIENT_ID and CLIENT_SECRET
```javascript
export const getClient = () => {
return {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET,
};
};
```
- The ```CLIENT_ID``` acts as a website id for github to recognise the website.
- The ```CLIENT_SECRET``` acts as the website secret for github to recognise the website.
- These ```.env``` variables must be set in fly for OAuth to work on the deployed site.
- These are set up in developer's github in:
```Settings/ developer settings/ gitHub Apps / appname```
### oauth

```javasccript!=
const TOKEN_URL = 'https://github.com/login/oauth/access_token';
export function getToken(code) {
const body = {
client_id: getClient().id,
client_secret: getClient().secret,
code,
};
return fetch(TOKEN_URL, {
method: 'POST',
body: JSON.stringify(body),
headers: { accept: 'application/json', 'content-type': 'application/json' },
}).then(getJson);
}
```
| then
---
```javascript!=
export default function auth(req, res) {
const code = req.query.code;
api
.getToken(code)
.then(api.getUser)
.then(user => {
res.cookie('refresh_token', user.refresh_token, {...} });
res.cookie('access_token', user.access_token, {...});
res.cookie('name', user.login, {...});
res.redirect('/');
})
.catch(() => res.redirect('/'));
}
```
| then
---
```javascript!=
const USER_URL = 'https://api.github.com/user';
export function getUser(user) {
return fetch(USER_URL, {
headers: {
accept: 'application/json',
authorization: `token ${user.access_token}`,
},
})
.then(async res => {
if (res.status != 401) return res;
const res2 = await refreshToken(res);
if (res2.status == 401) throw new Error('Refresh token failed');
return res2;
})
.then(res => res.json())
.then(json => {
return { ...user, ...json };
})
.catch(err => {
console.error('err getting user: ', err);
res.redirect('/');
});
}
```
{"metaMigratedAt":"2023-06-18T06:53:01.969Z","metaMigratedFrom":"Content","title":"SobSession","breaks":true,"contributors":"[{\"id\":\"c5c54e30-9692-49dc-baf9-f45297507939\",\"add\":2174,\"del\":704},{\"id\":\"5a3f4bdd-9e0a-4a00-b0eb-3ee951877df7\",\"add\":973,\"del\":1396},{\"id\":\"a210ffc0-41a5-4f68-b66f-05dcc31f5785\",\"add\":5759,\"del\":2268},{\"id\":\"fabff5d3-4757-44f9-9d3c-6ea0d5d4251f\",\"add\":1300,\"del\":81}]"}