Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
node_modules
bundle.js
notes.txt
53 changes: 33 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
# Chitter API Frontend Challenge

* Feel free to use Google, your notes, books, etc. but work on your own
* If you refer to the solution of another coach or student, please put a link to that in your README
* If you have a partial solution, **still check in a partial solution**
* You must submit a pull request to this repo with your code by 9am Monday morning
## Motivation

Challenge:
-------
The purpose of this challenge is to write a Single Page Application that makes calls to back end server for 'Chitter'. The following API interactions were provided by the server:

As usual please start by forking this repo.

We are going to write a small Twitter clone that will allow the users to post messages to a public stream.
* Creating Users
* Logging in
* Posting Peeps
* Viewing all Peeps *(I suggest you start here)*
* Viewing individual Peeps
* Deleting Peeps
* Liking Peeps
* Unliking Peeps

The scenario is similar to the [Chitter Challenge](https://github.com/makersacademy/chitter-challenge), except someone has already built a backend API for you and hosted it on Heroku.
## Functions Implemented

Your task is to build a front-end single-page-app to interface with this API. You can do this in any framework you like, or in pure Javascript. [The API documentation is here.](https://github.com/makersacademy/chitter_api_backend)
* Logging In - This requires users to already be signed up. With further time I would have implemented a signing up feature.
* Viewing Peeps - The page automatically renders the most recent 50 peeps.
* Posting Peeps - You can post a peep, which is then saved on the server.

Here are some interactions the API supports. Implement as many as you see fit.
## Functions to be Implemented

* Creating Users
* Logging in
* Posting Peeps
* Viewing all Peeps *(I suggest you start here)*
* Viewing individual Peeps
* Deleting Peeps
* Liking Peeps
* Unliking Peeps

We are looking for well tested, easy to read, easy to change code. This is more important than the number of interactions you implement.
## Structure

The Application is split into three classes, each with distinct responsibilities:

* ChitterApi - handles outgoing and incoming fetch requests with asynchronus fucntions.
* ChitterModel - manages and manipulates data (peeps) which come from the server.
* ChitterView - this is responsible for the functions of the user interface. It displays peeps and extracts data input by the user for requests sent by the API.

The application is rendered through an HTML file, which runs a bundled script of the classes above, plus an 'index' script, which executes the classes and functions on the web page.
With more time I would have stylised the page using CSS.

The backend server was already created by Makers. The API documentation are found through the following link:

Note that others may be doing the same task at the same time, so the data may change as you are using it.
[Chitter Backend](https://github.com/makersacademy/chitter-challenge)

## Utilities you might find useful
## How to Use

* [The Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) for making requests.
* [Postman](https://www.getpostman.com/) or [Insomnia](https://insomnia.rest/) for exploring the API.
- Fork and clone repository.
- Install jest, node and npm.
- To run tests, run 'jest' from the terminal.
- To open the page run 'open index.html' from the terminal.
58 changes: 58 additions & 0 deletions chitterApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
class ChitterApi {

fetchPeeps = (callback) => {
try {
fetch("https://chitter-backend-api-v2.herokuapp.com/peeps")
.then(response => response.json())
.then(data => callback(data));
} catch(err) {
return null;
};
};

userAuthorisation(handle, password, callback) {
try {
fetch('https://chitter-backend-api-v2.herokuapp.com/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"session": {
"handle":handle,
"password":password
}
})
})
.then(response => response.json())
.then(data => callback(data));
// catch doesn't seem to be working as I want it to.
// doesn't log the error in the format I want.
} catch (error) {
console.error('There was an error!', error);
}
}

postPeep(sessionKey, userID, peepContent, callback) {
try {
fetch('https://chitter-backend-api-v2.herokuapp.com/peeps', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Authorization": `Token token=${sessionKey}`
},
body: JSON.stringify({
"peep": {
"user_id": userID,
"body": peepContent
}})
})
.then(response => response.json())
.then(data => callback(data));
} catch (error) {
console.error('There was an error!', error);
}
}
}

module.exports = ChitterApi;
89 changes: 89 additions & 0 deletions chitterApi.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const ChitterApi = require('./chitterApi');

require('jest-fetch-mock').enableMocks();

describe('ChitterApi class', () => {

beforeEach(() => {
fetch.resetMocks();
})

it('fetches peeps using fetchPeeps', () => {
fetch.mockResponseOnce(JSON.stringify([{"id":1234, "body":"Testing testing 1,2"}]));

const api = new ChitterApi;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor comment, new ChitterApi could be moved to the before stattement

api.fetchPeeps((peepsObject) => {
let peeps = peepsObject;
expect(peeps[0].body).toEqual("Testing testing 1,2");
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith(
"https://chitter-backend-api-v2.herokuapp.com/peeps"
);
});
})

it('catches errors', () => {
fetch.mockReject(() => 'API Failure');

const api = new ChitterApi;
api.fetchPeeps((peepsObject) => {
expect(peepsObject).toEqual(null);
})
})

it('sends authorisation requests and returns session keys', () => {
fetch.mockResponseOnce(JSON.stringify({
"user_id":12345,
"session_key":"_3b_65_WEjfcW0unkmN9uVtIMa24f"
}));

const api = new ChitterApi;
api.userAuthorisation('testName', 'password123', (result) => {
expect(result.user_id).toEqual(12345);
expect(result.session_key).toEqual("_3b_65_WEjfcW0unkmN9uVtIMa24f");
})
})

it('raises an error when wrong credentials are provided', () => {
// I'd like to write this test so that I'm checkng for an error instead of a string
// but I can't figure out the syntax.
fetch.mockResponseOnce(JSON.stringify({
"errors":{
"password":"Invalid username or password"
}
}));

const api = new ChitterApi;
api.userAuthorisation('testName', 'password123', (result) => {
expect(result.errors.password).toEqual("Invalid username or password");
})
})

it('sends a peep to server and recieves confirmation', () => {
fetch.mockResponseOnce(JSON.stringify({
"id": 3,
"body": "my first peep :)",
"created_at": "2018-06-23T13:21:23.317Z",
"updated_at": "2018-06-23T13:21:23.317Z",
"user": {
"id": 1,
"handle": "kay"
},
"likes": [{
"user": {
"id": 1,
"handle": "kay"
}
}]
}));

const api = new ChitterApi;
let sessionKey = "_3b_65_WEjfcW0unkmN9uVtIMa24f";
let userID = 1;
let peepContent = "my first peep :)";
api.postPeep(sessionKey, userID, peepContent, (result) => {
expect(result.body).toEqual(peepContent);
})
})
})

21 changes: 21 additions & 0 deletions chitterModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class ChitterModel {

constructor(peeps = []) {
this.loadedPeeps = peeps;
}

loadPeeps(peeps) {
peeps.forEach((peep) => {
this.loadedPeeps.push(peep);
})
console.log('Loaded peeps are as follows:')
console.log(this.loadedPeeps);
}

returnLoadedPeeps() {
return this.loadedPeeps;
}

}

module.exports = ChitterModel
45 changes: 45 additions & 0 deletions chitterModel.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const ChitterModel = require('./chittermodel');

describe("ChitterModel class", () => {

it('returns empty array of notes when none have been added', () => {
const model = new ChitterModel;
expect(model.returnLoadedPeeps()).toEqual([]);
});

it('returns peeps loaded into model', () => {
const testPeep = [{
"id":12345,
"body":"this is a test",
"created_at":"2022-06-03T17:43:02.492Z",
"updated_at":"2022-06-03T17:43:02.492Z",
"user":{
"id":678,
"handle":"testUser"
},
"likes":[]
}];

const model = new ChitterModel(testPeep);
expect(model.returnLoadedPeeps()).toEqual(testPeep);
})

it('loads peeps into the model', () => {
const testPeep = [{
"id":12345,
"body":"this is a test",
"created_at":"2022-06-03T17:43:02.492Z",
"updated_at":"2022-06-03T17:43:02.492Z",
"user":{
"id":678,
"handle":"testUser"
},
"likes":[]
}];

const model = new ChitterModel
model.loadPeeps(testPeep);
expect(model.returnLoadedPeeps()).toEqual(testPeep);
})

})
61 changes: 61 additions & 0 deletions chitterView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
class ChitterView {

constructor(model, api) {
this.model = model;
this.api = api;

this.userID = null;
this.sessionKey = null;

this.peepContainerEl = document.querySelector('#peep-container');
this.loginButtonEl = document.querySelector('#login-button');
this.loginButtonEl.addEventListener('click', () => {
this.checkAuthorisation();
});
this.newPeepButtonEl = document.querySelector('#peep-button');
this.newPeepButtonEl.addEventListener('click', () => {
this.newPeepToApi();
});
}

importPeepsFromServer() {
this.api.fetchPeeps(peeps => {
console.log('Peeps retrieved from server:');
console.log(peeps);
this.model.loadPeeps(peeps);
this.displayPeeps();
})
}

displayPeeps() {
let peeps = this.model.returnLoadedPeeps();
peeps.forEach((peep) => {
const peepEl = document.createElement('div');
peepEl.innerText = peep.body;
peepEl.className = 'peep';
this.peepContainerEl.append(peepEl);
});
};

checkAuthorisation() {
let userHandle = document.querySelector('#handle').value;
let userPassword = document.querySelector('#password').value;
this.api.userAuthorisation(userHandle, userPassword, response => {
this.userID = response.user_id;
this.sessionKey = response.session_key;
console.log(this.userID, this.sessionKey);
});
}

newPeepToApi() {
if (this.sessionKey === null) {
throw new Error('Not Logged In');
};
let peepContent = document.querySelector('#new-peep').value;
this.api.postPeep(this.sessionKey, this.userID, peepContent, (result) => {
console.log(result);
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the page display the newly added peep automatically?

};

module.exports = ChitterView;
Loading